Skip to content

Commit 34124a8

Browse files
committed
[1] Implement CancelDelayed() and PostDelayed() for DispatcherSynchronizationContext.
[2] Add DispatcherScheduledAction.
1 parent 42e11a1 commit 34124a8

File tree

4 files changed

+183
-1
lines changed

4 files changed

+183
-1
lines changed

Avalonia.Tests/MainWindow.axaml.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
using Avalonia.Controls.Shapes;
55
using Avalonia.Markup.Xaml;
66
using Avalonia.Media;
7+
using Avalonia.Threading;
78
using CarinaStudio.Controls;
89
using CarinaStudio.Input.Platform;
10+
using CarinaStudio.Threading;
911
using System;
12+
using System.Diagnostics;
1013

1114
namespace CarinaStudio
1215
{
13-
partial class MainWindow : Avalonia.Controls.Window
16+
partial class MainWindow : Controls.Window
1417
{
1518
static readonly DirectProperty<MainWindow, DateTime?> DateTimeValueProperty = AvaloniaProperty.RegisterDirect<MainWindow, DateTime?>(nameof(DateTimeValue),
1619
w => w.dateTimeValue,
@@ -25,11 +28,23 @@ partial class MainWindow : Avalonia.Controls.Window
2528
DateTime? dateTimeValue = DateTime.Now;
2629
IPAddress? ipAddressObject = IPAddress.Loopback;
2730
string longText = "";
31+
readonly DispatcherScheduledAction scheduledAction;
32+
readonly Stopwatch stopwatch = new Stopwatch().Also(it => it.Start());
2833

2934
public MainWindow()
3035
{
3136
this.RefreshLongText();
3237
AvaloniaXamlLoader.Load(this);
38+
39+
var startTime = 0L;
40+
this.scheduledAction = new(this, () =>
41+
{
42+
var duration = (this.stopwatch.ElapsedMilliseconds - startTime);
43+
}, DispatcherPriority.Send);
44+
45+
startTime = this.stopwatch.ElapsedMilliseconds;
46+
this.scheduledAction.Schedule(5566);
47+
//this.scheduledAction.Cancel();
3348
}
3449

3550

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using Avalonia.Threading;
2+
using System;
3+
using System.Threading;
4+
5+
namespace CarinaStudio.Threading;
6+
7+
/// <summary>
8+
/// <see cref="ScheduledAction"/> which schedules the action with specific <see cref="DispatcherPriority"/>.
9+
/// </summary>
10+
public class DispatcherScheduledAction : ScheduledAction
11+
{
12+
// Fields.
13+
readonly DispatcherPriority priority;
14+
15+
16+
/// <summary>
17+
/// Initialize new <see cref="DispatcherScheduledAction"/> instance with <see cref="DispatcherPriority.Default"/> priority.
18+
/// </summary>
19+
/// <param name="synchronizationContext"><see cref="DispatcherSynchronizationContext"/> to perform action.</param>
20+
/// <param name="action">Action.</param>
21+
public DispatcherScheduledAction(DispatcherSynchronizationContext synchronizationContext, Action action) : this(synchronizationContext, action, DispatcherPriority.Default)
22+
{ }
23+
24+
25+
/// <summary>
26+
/// Initialize new <see cref="DispatcherScheduledAction"/> instance.
27+
/// </summary>
28+
/// <param name="synchronizationContext"><see cref="DispatcherSynchronizationContext"/> to perform action.</param>
29+
/// <param name="action">Action.</param>
30+
/// <param name="priority">Priority.</param>
31+
public DispatcherScheduledAction(DispatcherSynchronizationContext synchronizationContext, Action action, DispatcherPriority priority) : base(synchronizationContext, action)
32+
{
33+
this.priority = priority;
34+
}
35+
36+
37+
/// <summary>
38+
/// Initialize new <see cref="DispatcherScheduledAction"/> instance with <see cref="DispatcherPriority.Default"/> priority.
39+
/// </summary>
40+
/// <param name="synchronizable"><see cref="ISynchronizable"/> to provide <see cref="DispatcherSynchronizationContext"/> to perform action.</param>
41+
/// <param name="action">Action.</param>
42+
public DispatcherScheduledAction(ISynchronizable synchronizable, Action action) : this((DispatcherSynchronizationContext)synchronizable.SynchronizationContext, action, DispatcherPriority.Default)
43+
{ }
44+
45+
46+
/// <summary>
47+
/// Initialize new <see cref="DispatcherScheduledAction"/> instance.
48+
/// </summary>
49+
/// <param name="synchronizable"><see cref="ISynchronizable"/> to provide <see cref="DispatcherSynchronizationContext"/> to perform action.</param>
50+
/// <param name="action">Action.</param>
51+
/// <param name="priority">Priority.</param>
52+
public DispatcherScheduledAction(ISynchronizable synchronizable, Action action, DispatcherPriority priority) : this((DispatcherSynchronizationContext)synchronizable.SynchronizationContext, action, priority)
53+
{ }
54+
55+
56+
/// <inheritdoc/>
57+
protected override bool CancelAction(object token) =>
58+
((DispatcherSynchronizationContext)this.SynchronizationContext).CancelDelayed(token);
59+
60+
61+
/// <inheritdoc/>
62+
protected override object PostAction(SendOrPostCallback action, object? state, int delayMillis) =>
63+
((DispatcherSynchronizationContext)this.SynchronizationContext).PostDelayed(action, state, this.priority, delayMillis);
64+
}

Avalonia/Threading/DispatcherSynchronizationContext.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,66 @@ namespace CarinaStudio.Threading;
99
/// </summary>
1010
public class DispatcherSynchronizationContext : SynchronizationContext
1111
{
12+
// Stub of delayed call-back.
13+
class DelayedCallbackStub : IDelayedCallbackStub
14+
{
15+
// Fields.
16+
readonly Action? actionCallback;
17+
public readonly Dispatcher Dispatcher;
18+
volatile bool isCancellable = true;
19+
volatile bool isCancelled;
20+
readonly DispatcherPriority priority;
21+
readonly SendOrPostCallback? sendOrPostCallback;
22+
readonly object? state;
23+
24+
// Constructor.
25+
public DelayedCallbackStub(Dispatcher dispatcher, Action action, DispatcherPriority priority)
26+
{
27+
this.actionCallback = action;
28+
this.Dispatcher = dispatcher;
29+
this.priority = priority;
30+
}
31+
public DelayedCallbackStub(Dispatcher dispatcher, SendOrPostCallback callback, object? state, DispatcherPriority priority)
32+
{
33+
this.Dispatcher = dispatcher;
34+
this.priority = priority;
35+
this.sendOrPostCallback = callback;
36+
this.state = state;
37+
}
38+
39+
/// <inheritdoc/>
40+
void IDelayedCallbackStub.Callback() =>
41+
this.Dispatcher.Post(this.CallbackEntry, this.priority);
42+
43+
// Entry of call-back.
44+
void CallbackEntry()
45+
{
46+
lock (this)
47+
{
48+
if (this.isCancelled)
49+
return;
50+
this.isCancellable = false;
51+
}
52+
if (this.actionCallback is not null)
53+
this.actionCallback();
54+
else if (this.sendOrPostCallback is not null)
55+
this.sendOrPostCallback(this.state);
56+
}
57+
58+
/// <inheritdoc/>
59+
bool IDelayedCallbackStub.Cancel()
60+
{
61+
lock (this)
62+
{
63+
if (this.isCancelled || !this.isCancellable)
64+
return false;
65+
this.isCancelled = true;
66+
}
67+
return true;
68+
}
69+
}
70+
71+
1272
// Static fields.
1373
static volatile DispatcherSynchronizationContext? UIThreadInstance;
1474

@@ -25,6 +85,23 @@ public DispatcherSynchronizationContext(Dispatcher dispatcher)
2585
{
2686
this.dispatcher = dispatcher;
2787
}
88+
89+
90+
/// <summary>
91+
/// Cancel posted delayed call-back.
92+
/// </summary>
93+
/// <param name="token">Token returned from <see cref="PostDelayed(SendOrPostCallback, object?, DispatcherPriority, int)"/> or <see cref="PostDelayed(Action, DispatcherPriority, int)"/>.</param>
94+
/// <returns>True if call-back cancelled successfully.</returns>
95+
public bool CancelDelayed(object token)
96+
{
97+
if (!DelayedCallbacks.TryGetCallbackStub(token, out var callbackStub)
98+
|| callbackStub is not DelayedCallbackStub delayedCallbackStub
99+
|| delayedCallbackStub.Dispatcher != this.dispatcher)
100+
{
101+
return false;
102+
}
103+
return DelayedCallbacks.Cancel(token);
104+
}
28105

29106

30107
/// <summary>
@@ -76,6 +153,29 @@ public void Post(SendOrPostCallback d, object? state, DispatcherPriority priorit
76153
/// <param name="priority">Priority.</param>
77154
public void Post(Action action, DispatcherPriority priority) =>
78155
this.dispatcher.Post(action, priority);
156+
157+
158+
/// <summary>
159+
/// Post delayed call-back.
160+
/// </summary>
161+
/// <param name="callback">Call-back.</param>
162+
/// <param name="priority">Priority.</param>
163+
/// <param name="delayMillis">Delayed time in milliseconds.</param>
164+
/// <returns>Token of posted delayed call-back.</returns>
165+
public object PostDelayed(Action callback, DispatcherPriority priority, int delayMillis) =>
166+
DelayedCallbacks.Schedule(new DelayedCallbackStub(this.dispatcher, callback, priority), delayMillis);
167+
168+
169+
/// <summary>
170+
/// Post delayed call-back.
171+
/// </summary>
172+
/// <param name="callback">Call-back.</param>
173+
/// <param name="state">Custom state pass to call-back.</param>
174+
/// <param name="priority">Priority.</param>
175+
/// <param name="delayMillis">Delayed time in milliseconds.</param>
176+
/// <returns>Token of posted delayed call-back.</returns>
177+
public object PostDelayed(SendOrPostCallback callback, object? state, DispatcherPriority priority, int delayMillis) =>
178+
DelayedCallbacks.Schedule(new DelayedCallbackStub(this.dispatcher, callback, state, priority), delayMillis);
79179

80180

81181
/// <inheritdoc/>

Core/Core.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
2626
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
2727
</AssemblyAttribute>
28+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
29+
<_Parameter1>CarinaStudio.AppBase.Avalonia</_Parameter1>
30+
</AssemblyAttribute>
2831
</ItemGroup>
2932

3033
</Project>

0 commit comments

Comments
 (0)