diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
index e906931736..80b62b3818 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
@@ -538,4 +538,16 @@ public partial class Dispatcher
_ = action ?? throw new ArgumentNullException(nameof(action));
InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None);
}
+
+ ///
+ /// Posts an action that will be invoked on the dispatcher thread.
+ ///
+ /// The method.
+ /// The argument of method to call.
+ /// The priority with which to invoke the method.
+ public void Post(SendOrPostCallback action, object? arg, DispatcherPriority priority = default)
+ {
+ _ = action ?? throw new ArgumentNullException(nameof(action));
+ InvokeAsyncImpl(new SendOrPostCallbackDispatcherOperation(this, priority, action, arg, true), CancellationToken.None);
+ }
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
index bf34e80b61..0ce2479a45 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
@@ -47,6 +47,20 @@ public partial class Dispatcher
}
}
+ class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
+ {
+ public bool CurrentThreadIsLoopThread => true;
+ public void Signal()
+ {
+ }
+
+ public event Action? Signaled;
+ public event Action? Timer;
+ public void UpdateTimer(int? dueTimeInTicks)
+ {
+ }
+ }
+
internal static void ResetForUnitTests()
{
if (s_uiThread == null)
@@ -54,6 +68,8 @@ public partial class Dispatcher
var st = Stopwatch.StartNew();
while (true)
{
+ s_uiThread._pendingInputImpl = s_uiThread._controlledImpl = null;
+ s_uiThread._impl = new DummyShuttingDownUnitTestDispatcherImpl();
if (st.Elapsed.TotalSeconds > 5)
throw new InvalidProgramException("You've caused dispatcher loop");
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs
index 07c063ee31..e71e7375d3 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Avalonia.Threading;
@@ -168,4 +169,7 @@ public partial class Dispatcher
UpdateOSTimer();
}
}
+
+ internal static List SnapshotTimersForUnitTests() =>
+ s_uiThread!._timers.Where(t => t != s_uiThread._backgroundTimer).ToList();
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs
index 14808f00a1..9a0dacfbb8 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.cs
@@ -16,13 +16,13 @@ namespace Avalonia.Threading;
///
public partial class Dispatcher : IDispatcher
{
- private readonly IDispatcherImpl _impl;
+ private IDispatcherImpl _impl;
internal IDispatcherClock Clock { get; }
internal object InstanceLock { get; } = new();
private bool _hasShutdownFinished;
- private readonly IControlledDispatcherImpl? _controlledImpl;
+ private IControlledDispatcherImpl? _controlledImpl;
private static Dispatcher? s_uiThread;
- private readonly IDispatcherImplWithPendingInput? _pendingInputImpl;
+ private IDispatcherImplWithPendingInput? _pendingInputImpl;
internal Dispatcher(IDispatcherImpl impl, IDispatcherClock clock)
{
diff --git a/src/Avalonia.Base/Threading/DispatcherOperation.cs b/src/Avalonia.Base/Threading/DispatcherOperation.cs
index 5d2eab5a59..b0d7e5a049 100644
--- a/src/Avalonia.Base/Threading/DispatcherOperation.cs
+++ b/src/Avalonia.Base/Threading/DispatcherOperation.cs
@@ -261,6 +261,45 @@ public class DispatcherOperation : DispatcherOperation
}
}
+internal class SendOrPostCallbackDispatcherOperation : DispatcherOperation
+{
+ private readonly object? _arg;
+
+ internal SendOrPostCallbackDispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority,
+ SendOrPostCallback callback, object? arg, bool throwOnUiThread)
+ : base(dispatcher, priority, throwOnUiThread)
+ {
+ Callback = callback;
+ _arg = arg;
+ }
+
+ protected override void InvokeCore()
+ {
+ try
+ {
+ ((SendOrPostCallback)Callback!)(_arg);
+ lock (Dispatcher.InstanceLock)
+ {
+ Status = DispatcherOperationStatus.Completed;
+ if (TaskSource is TaskCompletionSource