diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index b729164b3b..fbf4000a79 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -307,6 +307,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0001
+ T:Avalonia.Controls.Platform.ManagedDispatcherImpl
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0001
T:Avalonia.Controls.Primitives.ChromeOverlayLayer
@@ -697,6 +703,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0001
+ T:Avalonia.Controls.Platform.ManagedDispatcherImpl
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0001
T:Avalonia.Controls.Primitives.ChromeOverlayLayer
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index 5316a84570..7a3059cb65 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -81,12 +81,12 @@ namespace Avalonia.Android
{
Options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions();
+ Dispatcher.InitializeUIThreadDispatcher(new AndroidDispatcherImpl());
AvaloniaLocator.CurrentMutable
.Bind().ToTransient()
.Bind().ToConstant(new WindowingPlatformStub())
.Bind().ToSingleton()
.Bind().ToSingleton()
- .Bind().ToConstant(new AndroidDispatcherImpl())
.Bind().ToSingleton()
.Bind().ToConstant(new ChoreographerTimer())
.Bind().ToSingleton()
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 3303ed276e..3ad488b615 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -34,7 +34,6 @@ namespace Avalonia
///
public AvaloniaObject()
{
- VerifyAccess();
_values = new ValueStore(this);
}
@@ -109,16 +108,22 @@ namespace Avalonia
///
internal string DebugDisplay => GetDebugDisplay(true);
+ ///
+ /// Returns the that this
+ /// is associated with.
+ ///
+ public Dispatcher Dispatcher { get; } = Dispatcher.CurrentDispatcher;
+
///
/// Returns a value indicating whether the current thread is the UI thread.
///
/// true if the current thread is the UI thread; otherwise false.
- public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
-
+ public bool CheckAccess() => Dispatcher.CheckAccess();
+
///
/// Checks that the current thread is the UI thread and throws if not.
///
- public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
+ public void VerifyAccess() => Dispatcher.VerifyAccess();
///
/// Clears a 's local value.
diff --git a/src/Avalonia.Base/Input/InputManager.cs b/src/Avalonia.Base/Input/InputManager.cs
index c9b1751b2a..7f5b5f82e7 100644
--- a/src/Avalonia.Base/Input/InputManager.cs
+++ b/src/Avalonia.Base/Input/InputManager.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Input
/// Receives input from the windowing subsystem and dispatches it to interested parties
/// for processing.
///
- internal class InputManager : IInputManager
+ internal class InputManager : IInputManager, IDisposable
{
private readonly LightweightSubject _preProcess = new();
private readonly LightweightSubject _process = new();
@@ -36,5 +36,12 @@ namespace Avalonia.Input
_process.OnNext(e);
_postProcess.OnNext(e);
}
+
+ public void Dispose()
+ {
+ _preProcess.OnCompleted();
+ _process.OnCompleted();
+ _postProcess.OnCompleted();
+ }
}
}
diff --git a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs b/src/Avalonia.Base/Platform/ManagedDispatcherImpl.cs
similarity index 100%
rename from src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
rename to src/Avalonia.Base/Platform/ManagedDispatcherImpl.cs
diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
index 6aabb4d168..7e1c9e711f 100644
--- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
@@ -30,9 +31,7 @@ internal abstract class BatchStreamPoolBase : IDisposable
var updateRef = new WeakReference>(this);
if (
reclaimImmediately
- || (
- AvaloniaLocator.Current.GetService() == null
- && AvaloniaLocator.Current.GetService() == null))
+ || Dispatcher.FromThread(Thread.CurrentThread) == null)
_reclaimImmediately = true;
else
StartUpdateTimer(startTimer, updateRef);
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index d1b960390c..f511d1de2a 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -76,7 +76,7 @@ namespace Avalonia
public static readonly StyledProperty ThemeProperty =
AvaloniaProperty.Register(nameof(Theme));
- private static readonly ControlTheme s_invalidTheme = new ControlTheme();
+ [ThreadStatic] private static ControlTheme? s_invalidTheme;
private int _initCount;
private string? _name;
private Classes? _classes;
@@ -332,6 +332,9 @@ namespace Avalonia
///
IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent;
+ internal static ControlTheme InvalidTheme
+ => s_invalidTheme ??= new();
+
///
public virtual void BeginInit()
{
@@ -666,10 +669,10 @@ namespace Avalonia
if (this.TryFindResource(key, out var value) && value is ControlTheme t)
_implicitTheme = t;
else
- _implicitTheme = s_invalidTheme;
+ _implicitTheme = InvalidTheme;
}
- if (_implicitTheme != s_invalidTheme)
+ if (_implicitTheme != InvalidTheme)
return _implicitTheme;
return null;
@@ -828,11 +831,11 @@ namespace Avalonia
return;
// Refetch the implicit theme.
- var oldImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
+ var oldImplicitTheme = _implicitTheme == InvalidTheme ? null : _implicitTheme;
_implicitTheme = null;
GetEffectiveTheme();
- var newImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
+ var newImplicitTheme = _implicitTheme == InvalidTheme ? null : _implicitTheme;
// If the implicit theme has changed, detach the existing theme.
if (newImplicitTheme != oldImplicitTheme)
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
index 954183ffcc..09dd9f27ec 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs
@@ -12,7 +12,7 @@ public partial class Dispatcher
private bool _explicitBackgroundProcessingRequested;
private const int MaximumInputStarvationTimeInFallbackMode = 50;
private const int MaximumInputStarvationTimeInExplicitProcessingExplicitMode = 50;
- private readonly int _maximumInputStarvationTime;
+ private int _maximumInputStarvationTime;
void RequestBackgroundProcessing()
{
@@ -101,9 +101,9 @@ public partial class Dispatcher
internal static void ResetBeforeUnitTests()
{
- s_uiThread = null;
+ ResetGlobalState();
}
-
+
internal static void ResetForUnitTests()
{
if (s_uiThread == null)
@@ -122,14 +122,14 @@ public partial class Dispatcher
if (job == null || job.Priority <= DispatcherPriority.Inactive)
{
s_uiThread.ShutdownImpl();
- s_uiThread = null;
+ ResetGlobalState();
return;
}
s_uiThread.ExecuteJob(job);
}
-
}
+
private void ExecuteJob(DispatcherOperation job)
{
diff --git a/src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs b/src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs
new file mode 100644
index 0000000000..7d9d0b39cf
--- /dev/null
+++ b/src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using Avalonia.Controls.Platform;
+using Avalonia.Metadata;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Threading;
+
+public partial class Dispatcher
+{
+ [ThreadStatic]
+ private static DispatcherReferenceStorage? s_currentThreadDispatcher;
+ private static readonly object s_globalLock = new();
+ private static readonly ConditionalWeakTable s_dispatchers = new();
+
+ private static Dispatcher? s_uiThread;
+
+ // This class is needed PURELY for ResetForUnitTests, so we can reset s_currentThreadDispatcher for all threads
+ class DispatcherReferenceStorage
+ {
+ public WeakReference Reference = new(null!);
+ }
+
+ public static Dispatcher CurrentDispatcher
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ if (s_currentThreadDispatcher?.Reference.TryGetTarget(out var dispatcher) == true)
+ return dispatcher;
+
+ return new Dispatcher(null);
+ }
+ }
+
+ public static Dispatcher? FromThread(Thread thread)
+ {
+ lock (s_globalLock)
+ {
+ if (s_dispatchers.TryGetValue(thread, out var reference) && reference.Reference.TryGetTarget(out var dispatcher) == true)
+ return dispatcher;
+ return null;
+ }
+ }
+
+ public static Dispatcher UIThread
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ static Dispatcher GetUIThreadDispatcherSlow()
+ {
+ lock (s_globalLock)
+ {
+ return s_uiThread ?? CurrentDispatcher;
+ }
+ }
+ return s_uiThread ?? GetUIThreadDispatcherSlow();
+ }
+ }
+
+ internal static Dispatcher? TryGetUIThread()
+ {
+ lock (s_globalLock)
+ return s_uiThread;
+ }
+
+ [PrivateApi]
+ public static void InitializeUIThreadDispatcher(IPlatformThreadingInterface impl) =>
+ InitializeUIThreadDispatcher(new LegacyDispatcherImpl(impl));
+
+ [PrivateApi]
+ public static void InitializeUIThreadDispatcher(IDispatcherImpl impl)
+ {
+ UIThread.VerifyAccess();
+ if (UIThread._initialized)
+ throw new InvalidOperationException("UI thread dispatcher is already initialized");
+ UIThread.ReplaceImplementation(impl);
+ }
+
+ private static void ResetGlobalState()
+ {
+ lock (s_globalLock)
+ {
+ foreach (var store in s_dispatchers)
+ store.Value.Reference = new(null!);
+ s_dispatchers.Clear();
+
+ s_currentThreadDispatcher = null;
+ s_uiThread = null;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs
index ce16820286..9966a156d2 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.Diagnostics;
using System.Linq;
namespace Avalonia.Threading;
@@ -15,7 +16,8 @@ public partial class Dispatcher
private long? _dueTimeForBackgroundProcessing;
private long? _osTimerSetTo;
- internal long Now => _impl.Now;
+ private readonly Func _timeProvider;
+ internal long Now => _timeProvider();
private void UpdateOSTimer()
{
@@ -26,6 +28,7 @@ public partial class Dispatcher
_dueTimeForTimers ?? _dueTimeForBackgroundProcessing;
if (_osTimerSetTo == nextDueTime)
return;
+
_impl.UpdateTimer(_osTimerSetTo = nextDueTime);
}
@@ -114,7 +117,8 @@ public partial class Dispatcher
bool needToProcessQueue = false;
lock (InstanceLock)
{
- _impl.UpdateTimer(_osTimerSetTo = null);
+ _impl.UpdateTimer(null);
+ _osTimerSetTo = null;
needToPromoteTimers = _dueTimeForTimers.HasValue && _dueTimeForTimers.Value <= Now;
if (needToPromoteTimers)
_dueTimeForTimers = null;
diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs
index 8253c2fed2..07582ac3f4 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.cs
@@ -3,7 +3,10 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
+using Avalonia.Controls.Platform;
+using Avalonia.Metadata;
using Avalonia.Platform;
+using Avalonia.Utilities;
namespace Avalonia.Threading;
@@ -17,63 +20,60 @@ namespace Avalonia.Threading;
public partial class Dispatcher : IDispatcher
{
private IDispatcherImpl _impl;
+ private bool _initialized;
internal object InstanceLock { get; } = new();
private IControlledDispatcherImpl? _controlledImpl;
- private static Dispatcher? s_uiThread;
private IDispatcherImplWithPendingInput? _pendingInputImpl;
- private readonly IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl;
+ private IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl;
+ private readonly Thread _thread;
private readonly AvaloniaSynchronizationContext?[] _priorityContexts =
new AvaloniaSynchronizationContext?[DispatcherPriority.MaxValue - DispatcherPriority.MinValue + 1];
- internal Dispatcher(IDispatcherImpl impl)
+ internal Dispatcher(IDispatcherImpl? impl)
{
- _impl = impl;
- impl.Timer += OnOSTimer;
- impl.Signaled += Signaled;
- _controlledImpl = _impl as IControlledDispatcherImpl;
- _pendingInputImpl = _impl as IDispatcherImplWithPendingInput;
- _backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing;
- _maximumInputStarvationTime = _backgroundProcessingImpl == null ?
- MaximumInputStarvationTimeInFallbackMode :
- MaximumInputStarvationTimeInExplicitProcessingExplicitMode;
- if (_backgroundProcessingImpl != null)
- _backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing;
-
- _unhandledExceptionEventArgs = new DispatcherUnhandledExceptionEventArgs(this);
- _exceptionFilterEventArgs = new DispatcherUnhandledExceptionFilterEventArgs(this);
- }
-
- public static Dispatcher UIThread
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
+#if DEBUG
+ if (AvaloniaLocator.Current.GetService() != null
+ || AvaloniaLocator.Current.GetService() != null)
+ throw new InvalidOperationException(
+ "Registering IDispatcherImpl or IPlatformThreadingInterface via locator is no longer valid");
+#endif
+ lock (s_globalLock)
{
- return s_uiThread ??= CreateUIThreadDispatcher();
- }
- }
+ _thread = Thread.CurrentThread;
+ if (FromThread(_thread) != null)
+ throw new InvalidOperationException("The current thread already has a dispatcher");
- public bool SupportsRunLoops => _controlledImpl != null;
+ // The first created dispatcher becomes "UI thread one"
+ s_uiThread ??= this;
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static Dispatcher CreateUIThreadDispatcher()
- {
- var impl = AvaloniaLocator.Current.GetService();
- if (impl == null)
+ s_dispatchers.Remove(Thread.CurrentThread);
+ s_dispatchers.Add(Thread.CurrentThread,
+ s_currentThreadDispatcher = new() { Reference = new WeakReference(this) });
+ }
+
+ if (impl is null)
{
- var platformThreading = AvaloniaLocator.Current.GetService();
- if (platformThreading != null)
- impl = new LegacyDispatcherImpl(platformThreading);
- else
- impl = new NullDispatcherImpl();
+ var st = Stopwatch.StartNew();
+ _timeProvider = () => st.ElapsedMilliseconds;
}
- return new Dispatcher(impl);
+ else
+ _timeProvider = () => impl.Now;
+
+ _impl = null!; // Set by ReplaceImplementation
+ ReplaceImplementation(impl);
+
+
+ _unhandledExceptionEventArgs = new DispatcherUnhandledExceptionEventArgs(this);
+ _exceptionFilterEventArgs = new DispatcherUnhandledExceptionFilterEventArgs(this);
}
+ public bool SupportsRunLoops => _controlledImpl != null;
+
///
/// Checks that the current thread is the UI thread.
///
- public bool CheckAccess() => _impl.CurrentThreadIsLoopThread;
+ public bool CheckAccess() => Thread.CurrentThread == _thread;
///
/// Checks that the current thread is the UI thread and throws if not.
@@ -89,15 +89,64 @@ public partial class Dispatcher : IDispatcher
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowVerifyAccess()
- => throw new InvalidOperationException("Call from invalid thread");
+ => throw new InvalidOperationException("The calling thread cannot access this object because a different thread owns it.");
ThrowVerifyAccess();
}
}
+ public Thread Thread => _thread;
+
internal AvaloniaSynchronizationContext GetContextWithPriority(DispatcherPriority priority)
{
DispatcherPriority.Validate(priority, nameof(priority));
var index = priority - DispatcherPriority.MinValue;
return _priorityContexts[index] ??= new(this, priority);
}
+
+ [PrivateApi]
+ public IDispatcherImpl PlatformImpl => _impl;
+
+ private void ReplaceImplementation(IDispatcherImpl? impl)
+ {
+ // TODO: Consider moving the helper out of Avalonia.Win32 so
+ // it's usable earlier
+ using var _ = NonPumpingLockHelper.Use();
+
+
+ if (impl?.CurrentThreadIsLoopThread == false)
+ throw new InvalidOperationException("IDispatcherImpl belongs to a different thread");
+
+ if (_impl != null!) // Null in ctor
+ {
+ _impl.Timer -= OnOSTimer;
+ _impl.Signaled -= Signaled;
+ if (_backgroundProcessingImpl != null)
+ _backgroundProcessingImpl.ReadyForBackgroundProcessing -= OnReadyForExplicitBackgroundProcessing;
+ _impl = null!;
+ _controlledImpl = null;
+ _pendingInputImpl = null;
+ _backgroundProcessingImpl = null;
+ }
+
+ if (impl != null)
+ _initialized = true;
+ else
+ impl = new ManagedDispatcherImpl(null);
+ _impl = impl;
+
+ impl.Timer += OnOSTimer;
+ impl.Signaled += Signaled;
+ _controlledImpl = _impl as IControlledDispatcherImpl;
+ _pendingInputImpl = _impl as IDispatcherImplWithPendingInput;
+ _backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing;
+ _maximumInputStarvationTime = _backgroundProcessingImpl == null ?
+ MaximumInputStarvationTimeInFallbackMode :
+ MaximumInputStarvationTimeInExplicitProcessingExplicitMode;
+ if (_backgroundProcessingImpl != null)
+ _backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing;
+ if (_signaled)
+ _impl.Signal();
+ _osTimerSetTo = null;
+ UpdateOSTimer();
+ }
}
diff --git a/src/Avalonia.Base/Threading/IDispatcherImpl.cs b/src/Avalonia.Base/Threading/IDispatcherImpl.cs
index dd438b176e..f8d5cb8947 100644
--- a/src/Avalonia.Base/Threading/IDispatcherImpl.cs
+++ b/src/Avalonia.Base/Threading/IDispatcherImpl.cs
@@ -80,33 +80,4 @@ internal class LegacyDispatcherImpl : IDispatcherImpl
_timer = null;
Timer?.Invoke();
}
-}
-
-internal sealed class NullDispatcherImpl : IDispatcherImpl
-{
- public bool CurrentThreadIsLoopThread => true;
-
- public void Signal()
- {
-
- }
-
- public event Action? Signaled
- {
- add { }
- remove { }
- }
-
- public event Action? Timer
- {
- add { }
- remove { }
- }
-
- public long Now => 0;
-
- public void UpdateTimer(long? dueTimeInMs)
- {
-
- }
-}
+}
\ No newline at end of file
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
index dcc24482c0..43eddb010d 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
@@ -55,7 +55,6 @@ namespace Avalonia.DesignerSupport.Remote
.Bind().ToSingleton()
.Bind().ToConstant(Keyboard)
.Bind().ToSingleton()
- .Bind().ToConstant(new ManagedDispatcherImpl(null))
.Bind().ToConstant(new UiThreadRenderTimer(60))
.Bind().ToConstant(instance)
.Bind().ToSingleton()
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index 88d809e47d..40bc2ca71e 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -114,8 +114,8 @@ namespace Avalonia.Native
var clipboardImpl = new ClipboardImpl(_factory.CreateClipboard());
var clipboard = new Clipboard(clipboardImpl);
+ Dispatcher.InitializeUIThreadDispatcher(new DispatcherImpl(_factory.CreatePlatformThreadingInterface()));
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new DispatcherImpl(_factory.CreatePlatformThreadingInterface()))
.Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind().ToConstant(new ScreenImpl(_factory.CreateScreens))
.Bind().ToSingleton()
diff --git a/src/Avalonia.Native/CallbackBase.cs b/src/Avalonia.Native/CallbackBase.cs
index c5978e2a0d..04ca37b4b9 100644
--- a/src/Avalonia.Native/CallbackBase.cs
+++ b/src/Avalonia.Native/CallbackBase.cs
@@ -1,5 +1,6 @@
using System;
using System.Runtime.ExceptionServices;
+using System.Threading;
using Avalonia.MicroCom;
using Avalonia.Platform;
using Avalonia.Threading;
@@ -11,7 +12,7 @@ namespace Avalonia.Native
{
public void RaiseException(Exception e)
{
- if (AvaloniaLocator.Current.GetService() is DispatcherImpl dispatcherImpl)
+ if(Dispatcher.FromThread(Thread.CurrentThread) is { PlatformImpl: DispatcherImpl dispatcherImpl })
{
dispatcherImpl.PropagateCallbackException(ExceptionDispatchInfo.Capture(e));
}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index e685ff4afb..ec8a915ea0 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -42,6 +42,7 @@ namespace Avalonia.X11
public X11Globals Globals { get; private set; } = null!;
public XResources Resources { get; private set; } = null!;
public ManualRawEventGrouperDispatchQueue EventGrouperDispatchQueue { get; } = new();
+ public IX11PlatformDispatcher DispatcherImpl { get; private set; } = null!;
public void Initialize(X11PlatformOptions options)
{
@@ -79,10 +80,12 @@ namespace Avalonia.X11
var clipboard = new Input.Platform.Clipboard(clipboardImpl);
AvaloniaLocator.CurrentMutable.BindToSelf(this)
- .Bind().ToConstant(this)
- .Bind().ToConstant(options.UseGLibMainLoop
- ? new GlibDispatcherImpl(this)
- : new X11PlatformThreading(this))
+ .Bind().ToConstant(this);
+ DispatcherImpl = options.UseGLibMainLoop
+ ? new GlibDispatcherImpl(this)
+ : new X11PlatformThreading(this);
+ Dispatcher.InitializeUIThreadDispatcher(DispatcherImpl);
+ AvaloniaLocator.CurrentMutable
.Bind().ToConstant(timer)
.Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control))
.Bind().ToConstant(new KeyGestureFormatInfo(new Dictionary() { }, meta: "Super"))
diff --git a/src/Avalonia.X11/XEmbedPlug.cs b/src/Avalonia.X11/XEmbedPlug.cs
index f0e3df688e..3ea53f2ab9 100644
--- a/src/Avalonia.X11/XEmbedPlug.cs
+++ b/src/Avalonia.X11/XEmbedPlug.cs
@@ -13,19 +13,20 @@ public class XEmbedPlug : IDisposable
private Color _backgroundColor;
private readonly X11Info _x11;
private readonly X11Window.XEmbedClientWindowMode _mode;
+ private readonly AvaloniaX11Platform _platform;
private XEmbedPlug(IntPtr? parentXid)
{
- var platform = AvaloniaLocator.Current.GetRequiredService();
+ _platform = AvaloniaLocator.Current.GetRequiredService();
_mode = new X11Window.XEmbedClientWindowMode();
- _root = new EmbeddableControlRoot(new X11Window(platform, null, _mode));
+ _root = new EmbeddableControlRoot(new X11Window(_platform, null, _mode));
_root.Prepare();
- _x11 = platform.Info;
+ _x11 = _platform.Info;
if (parentXid.HasValue)
- XLib.XReparentWindow(platform.Display, Handle, parentXid.Value, 0, 0);
+ XLib.XReparentWindow(_platform.Display, Handle, parentXid.Value, 0, 0);
// Make sure that the newly created XID is visible for other clients
- XLib.XSync(platform.Display, false);
+ XLib.XSync(_platform.Display, false);
}
private EmbeddableControlRoot Root
@@ -60,8 +61,7 @@ public class XEmbedPlug : IDisposable
public void ProcessInteractiveResize(PixelSize size)
{
-
- var events = (IX11PlatformDispatcher)AvaloniaLocator.Current.GetRequiredService();
+ var events = _platform.DispatcherImpl;
events.EventDispatcher.DispatchX11Events(CancellationToken.None);
_mode.ProcessInteractiveResize(size);
Dispatcher.UIThread.RunJobs(DispatcherPriority.UiThreadRender);
diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
index 33b9521ae2..016c598716 100644
--- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs
+++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs
@@ -97,15 +97,17 @@ internal class BrowserWindowingPlatform : IWindowingPlatform
.Bind().ToSingleton()
.Bind().ToConstant(new KeyGestureFormatInfo(new Dictionary() { }))
.Bind().ToSingleton();
+
if (IsManagedDispatcherEnabled)
{
EventGrouperDispatchQueue = new();
- AvaloniaLocator.CurrentMutable.Bind().ToConstant(
- new ManagedDispatcherImpl(new ManualRawEventGrouperDispatchQueueDispatcherInputProvider(EventGrouperDispatchQueue)));
+ Dispatcher.InitializeUIThreadDispatcher(
+ new ManagedDispatcherImpl(
+ new ManualRawEventGrouperDispatchQueueDispatcherInputProvider(EventGrouperDispatchQueue)));
}
else
{
- AvaloniaLocator.CurrentMutable.Bind().ToSingleton();
+ Dispatcher.InitializeUIThreadDispatcher(new BrowserDispatcherImpl());
}
// GC thread is the same as the main one when MT is disabled
diff --git a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
index 48c5f5d84e..b56e686d4b 100644
--- a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
+++ b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
@@ -79,7 +79,6 @@ namespace Avalonia.Headless
var clipboard = new Clipboard(clipboardImpl);
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new ManagedDispatcherImpl(null))
.Bind().ToConstant(clipboardImpl)
.Bind().ToConstant(clipboard)
.Bind().ToSingleton()
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
index 0d7d74e01c..ee8b85919e 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
@@ -62,8 +62,8 @@ namespace Avalonia.LinuxFramebuffer
? new UiThreadRenderTimer(opts.Fps)
: new DefaultRenderTimer(opts.Fps);
+ Dispatcher.InitializeUIThreadDispatcher(new EpollDispatcherImpl(new ManualRawEventGrouperDispatchQueueDispatcherInputProvider(EventGrouperDispatchQueue)));
AvaloniaLocator.CurrentMutable
- .Bind().ToConstant(new EpollDispatcherImpl(new ManualRawEventGrouperDispatchQueueDispatcherInputProvider(EventGrouperDispatchQueue)))
.Bind().ToConstant(timer)
.Bind().ToTransient()
.Bind().ToConstant(new KeyboardDevice())
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 41f273bc5e..f158d539ff 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -85,6 +85,8 @@ namespace Avalonia.Win32
SetDpiAwareness();
+ Dispatcher.InitializeUIThreadDispatcher(s_instance._dispatcher);
+
var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60);
var clipboardImpl = new ClipboardImpl();
var clipboard = new Clipboard(clipboardImpl);
@@ -96,7 +98,6 @@ namespace Avalonia.Win32
.Bind().ToConstant(WindowsKeyboardDevice.Instance)
.Bind().ToSingleton()
.Bind().ToSingleton()
- .Bind().ToConstant(s_instance._dispatcher)
.Bind().ToConstant(renderTimer)
.Bind().ToConstant(s_instance)
.Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control)
diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs
index 67152029af..29633a8609 100644
--- a/src/iOS/Avalonia.iOS/Platform.cs
+++ b/src/iOS/Avalonia.iOS/Platform.cs
@@ -77,6 +77,7 @@ namespace Avalonia.iOS
Timer ??= new DisplayLinkTimer();
var keyboard = new KeyboardDevice();
+ Dispatcher.InitializeUIThreadDispatcher(DispatcherImpl.Instance);
AvaloniaLocator.CurrentMutable
.Bind().ToConstant(Graphics)
.Bind().ToConstant(new CursorFactoryStub())
@@ -93,7 +94,6 @@ namespace Avalonia.iOS
{ Key.Up , "↑" }
}, ctrl: "⌃", meta: "⌘", shift: "⇧", alt: "⌥"))
.Bind().ToConstant(Timer)
- .Bind().ToConstant(DispatcherImpl.Instance)
.Bind().ToConstant(keyboard);
if (appDelegate is not null)
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index e47dca2391..fa1919feb4 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
@@ -19,7 +20,7 @@ using Xunit;
namespace Avalonia.Base.UnitTests
{
- public class AvaloniaObjectTests_Binding
+ public class AvaloniaObjectTests_Binding : ScopedTestBase
{
[Fact]
public void Bind_Sets_Current_Value()
@@ -858,37 +859,28 @@ namespace Avalonia.Base.UnitTests
[InlineData(BindingPriority.Style)]
public void Typed_Bind_Executes_On_UIThread(BindingPriority priority)
{
- AsyncContext.Run(async () =>
+ using (UnitTestApplication.Start())
{
var target = new Class1();
var source = new Subject();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var raised = 0;
- var dispatcherMock = new Mock();
- dispatcherMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
- .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
-
- var services = new TestServices(
- dispatcherImpl: dispatcherMock.Object);
-
target.PropertyChanged += (s, e) =>
{
Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId);
++raised;
};
- using (UnitTestApplication.Start(services))
- {
- target.Bind(Class1.FooProperty, source, priority);
- await Task.Run(() => source.OnNext("foobar"));
- Dispatcher.UIThread.RunJobs();
+ target.Bind(Class1.FooProperty, source, priority);
- Assert.Equal("foobar", target.GetValue(Class1.FooProperty));
- Assert.Equal(1, raised);
- }
- });
+ ThreadRunHelper.RunOnDedicatedThreadAndWait(() => source.OnNext("foobar"));
+ Dispatcher.UIThread.RunJobs(null, TestContext.Current.CancellationToken);
+
+ Assert.Equal("foobar", target.GetValue(Class1.FooProperty));
+ Assert.Equal(1, raised);
+ }
}
[Theory]
@@ -896,37 +888,28 @@ namespace Avalonia.Base.UnitTests
[InlineData(BindingPriority.Style)]
public void Untyped_Bind_Executes_On_UIThread(BindingPriority priority)
{
- AsyncContext.Run(async () =>
+ using (UnitTestApplication.Start())
{
var target = new Class1();
var source = new Subject