diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index 5639963e9e..a5fa41d534 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -1,12 +1,11 @@ using System; -using System.Runtime.ConstrainedExecution; using System.Threading; using Avalonia.Utilities; namespace Avalonia.Threading { /// - /// SynchronizationContext to be used on main thread + /// A that uses a to post messages. /// public class AvaloniaSynchronizationContext : SynchronizationContext { @@ -16,7 +15,7 @@ namespace Avalonia.Threading private readonly Dispatcher _dispatcher; // This constructor is here to enforce STA behavior for unit tests - internal AvaloniaSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority, bool isStaThread = false) + internal AvaloniaSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority, bool isStaThread) { _dispatcher = dispatcher; Priority = priority; @@ -26,17 +25,17 @@ namespace Avalonia.Threading } public AvaloniaSynchronizationContext() - : this(Dispatcher.UIThread, DispatcherPriority.Default, Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) + : this(Dispatcher.CurrentDispatcher, DispatcherPriority.Default) { } public AvaloniaSynchronizationContext(DispatcherPriority priority) - : this(Dispatcher.UIThread, priority, false) + : this(Dispatcher.CurrentDispatcher, priority) { } public AvaloniaSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority) - : this(dispatcher, priority, false) + : this(dispatcher, priority, dispatcher.IsSta) { } @@ -55,7 +54,7 @@ namespace Avalonia.Threading return; } - SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(DispatcherPriority.Normal)); + SetSynchronizationContext(Dispatcher.CurrentDispatcher.GetContextWithPriority(DispatcherPriority.Normal)); } /// @@ -105,7 +104,7 @@ namespace Avalonia.Threading } } - public static RestoreContext Ensure(DispatcherPriority priority) => Ensure(Dispatcher.UIThread, priority); + public static RestoreContext Ensure(DispatcherPriority priority) => Ensure(Dispatcher.CurrentDispatcher, priority); public static RestoreContext Ensure(Dispatcher dispatcher, DispatcherPriority priority) { if (Current is AvaloniaSynchronizationContext avaloniaContext diff --git a/src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs b/src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs index 7d9d0b39cf..eb0fbbcdf0 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs @@ -44,7 +44,14 @@ public partial class Dispatcher return null; } } - + + /// + /// Gets the dispatcher for the UI thread. + /// + /// + /// Control and libraries author are encouraged to use and + /// instead. + /// public static Dispatcher UIThread { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index 07582ac3f4..100df0acbc 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -13,10 +13,6 @@ namespace Avalonia.Threading; /// /// Provides services for managing work items on a thread. /// -/// -/// In Avalonia, there is usually only a single in the application - -/// the one for the UI thread, retrieved via the property. -/// public partial class Dispatcher : IDispatcher { private IDispatcherImpl _impl; @@ -52,6 +48,8 @@ public partial class Dispatcher : IDispatcher s_currentThreadDispatcher = new() { Reference = new WeakReference(this) }); } + IsSta = _thread.GetApartmentState() == ApartmentState.STA; + if (impl is null) { var st = Stopwatch.StartNew(); @@ -70,6 +68,8 @@ public partial class Dispatcher : IDispatcher public bool SupportsRunLoops => _controlledImpl != null; + internal bool IsSta { get; } + /// /// Checks that the current thread is the UI thread. /// diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index ea88109bdf..443b9ae89f 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -7,65 +7,102 @@ namespace Avalonia.Threading; /// A timer that is integrated into the Dispatcher queues, and will /// be processed after a given amount of time at a specified priority. /// -public partial class DispatcherTimer +public class DispatcherTimer { internal static int ActiveTimersCount { get; private set; } /// - /// Creates a timer that uses theUI thread's Dispatcher2 to - /// process the timer event at background priority. + /// Creates a timer that uses to + /// process the timer event at background priority. /// - public DispatcherTimer() : this(DispatcherPriority.Background) + public DispatcherTimer() + : this(TimeSpan.Zero, DispatcherPriority.Background, Dispatcher.CurrentDispatcher) { } /// - /// Creates a timer that uses the UI thread's Dispatcher2 to - /// process the timer event at the specified priority. + /// Creates a timer that uses to + /// process the timer event at the specified priority. /// - /// - /// The priority to process the timer at. - /// - public DispatcherTimer(DispatcherPriority priority) : this(Threading.Dispatcher.UIThread, priority, - TimeSpan.FromMilliseconds(0)) + /// The priority to process the timer at. + public DispatcherTimer(DispatcherPriority priority) + : this(TimeSpan.Zero, priority, Dispatcher.CurrentDispatcher) { } /// - /// Creates a timer that uses the specified Dispatcher2 to - /// process the timer event at the specified priority. + /// Creates a timer that uses the specified to + /// process the timer event at the specified priority. /// - /// - /// The priority to process the timer at. - /// - /// - /// The dispatcher to use to process the timer. - /// - internal DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher) : this(dispatcher, priority, - TimeSpan.FromMilliseconds(0)) + /// The priority to process the timer at. + /// The dispatcher to use to process the timer. + public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher) + : this(TimeSpan.Zero, priority, dispatcher) { } /// - /// Creates a timer that uses the UI thread's Dispatcher2 to - /// process the timer event at the specified priority after the specified timeout. + /// Creates a timer that uses the specified to + /// process the timer event at the specified priority after the specified timeout. /// - /// - /// The interval to tick the timer after. - /// - /// - /// The priority to process the timer at. - /// - /// - /// The callback to call when the timer ticks. - /// - public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback) - : this(Threading.Dispatcher.UIThread, priority, interval) + /// The interval to tick the timer after. + /// The priority to process the timer at. + /// The dispatcher to use to process the timer. + public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, Dispatcher dispatcher) { - if (callback == null) + ArgumentNullException.ThrowIfNull(dispatcher); + + DispatcherPriority.Validate(priority, "priority"); + if (priority == DispatcherPriority.Inactive) + { + throw new ArgumentException("Specified priority is not valid.", nameof(priority)); + } + + var ms = interval.TotalMilliseconds; + if (ms < 0) { - throw new ArgumentNullException(nameof(callback)); + throw new ArgumentOutOfRangeException(nameof(interval), + "TimeSpan period must be greater than or equal to zero."); } + if (ms > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(interval), + "TimeSpan period must be less than or equal to Int32.MaxValue."); + } + + _dispatcher = dispatcher; + _priority = priority; + _interval = interval; + } + + /// + /// Creates a timer that uses to + /// process the timer event at the specified priority after the specified timeout and with + /// the specified handler. + /// + /// The interval to tick the timer after. + /// The priority to process the timer at. + /// The callback to call when the timer ticks. + /// This constructor immediately starts the timer. + public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback) + : this(interval, priority, Dispatcher.CurrentDispatcher, callback) + { + } + + /// + /// Creates a timer that uses the specified to + /// process the timer event at the specified priority after the specified timeout and with + /// the specified handler. + /// + /// The interval to tick the timer after. + /// The priority to process the timer at. + /// The dispatcher to use to process the timer. + /// The callback to call when the timer ticks. + /// This constructor immediately starts the timer. + public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, Dispatcher dispatcher, EventHandler callback) + : this(interval, priority, dispatcher) + { + ArgumentNullException.ThrowIfNull(callback); Tick += callback; Start(); @@ -252,33 +289,6 @@ public partial class DispatcherTimer /// public object? Tag { get; set; } - - internal DispatcherTimer(Dispatcher dispatcher, DispatcherPriority priority, TimeSpan interval) - { - if (dispatcher == null) - { - throw new ArgumentNullException(nameof(dispatcher)); - } - - DispatcherPriority.Validate(priority, "priority"); - if (priority == DispatcherPriority.Inactive) - { - throw new ArgumentException("Specified priority is not valid.", nameof(priority)); - } - - if (interval.TotalMilliseconds < 0) - throw new ArgumentOutOfRangeException(nameof(interval), "TimeSpan period must be greater than or equal to zero."); - - if (interval.TotalMilliseconds > Int32.MaxValue) - throw new ArgumentOutOfRangeException(nameof(interval), - "TimeSpan period must be less than or equal to Int32.MaxValue."); - - - _dispatcher = dispatcher; - _priority = priority; - _interval = interval; - } - private void Restart() { lock (_instanceLock)