Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System;
using System.Runtime.ConstrainedExecution;
using System.Threading;
using Avalonia.Utilities;

namespace Avalonia.Threading
{
/// <summary>
/// SynchronizationContext to be used on main thread
/// A <see cref="SynchronizationContext"/> that uses a <see cref="Dispatcher"/> to post messages.
/// </summary>
public class AvaloniaSynchronizationContext : SynchronizationContext
{
Expand All @@ -16,7 +15,7 @@ public class AvaloniaSynchronizationContext : SynchronizationContext
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;
Expand All @@ -26,17 +25,17 @@ internal AvaloniaSynchronizationContext(Dispatcher dispatcher, DispatcherPriorit
}

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)
{
}

Expand All @@ -55,7 +54,7 @@ public static void InstallIfNeeded()
return;
}

SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(DispatcherPriority.Normal));
SetSynchronizationContext(Dispatcher.CurrentDispatcher.GetContextWithPriority(DispatcherPriority.Normal));
}

/// <inheritdoc/>
Expand Down Expand Up @@ -105,7 +104,7 @@ public void Dispose()
}
}

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
Expand Down
9 changes: 8 additions & 1 deletion src/Avalonia.Base/Threading/Dispatcher.ThreadStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,14 @@ public static Dispatcher CurrentDispatcher
return null;
}
}


/// <summary>
/// Gets the dispatcher for the UI thread.
/// </summary>
/// <remarks>
/// Control and libraries author are encouraged to use <see cref="CurrentDispatcher"/> and
/// <see cref="AvaloniaObject.Dispatcher"/> instead.
/// </remarks>
public static Dispatcher UIThread
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
8 changes: 4 additions & 4 deletions src/Avalonia.Base/Threading/Dispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ namespace Avalonia.Threading;
/// <summary>
/// Provides services for managing work items on a thread.
/// </summary>
/// <remarks>
/// In Avalonia, there is usually only a single <see cref="Dispatcher"/> in the application -
/// the one for the UI thread, retrieved via the <see cref="UIThread"/> property.
/// </remarks>
public partial class Dispatcher : IDispatcher
{
private IDispatcherImpl _impl;
Expand Down Expand Up @@ -52,6 +48,8 @@ internal Dispatcher(IDispatcherImpl? impl)
s_currentThreadDispatcher = new() { Reference = new WeakReference<Dispatcher>(this) });
}

IsSta = _thread.GetApartmentState() == ApartmentState.STA;

if (impl is null)
{
var st = Stopwatch.StartNew();
Expand All @@ -70,6 +68,8 @@ internal Dispatcher(IDispatcherImpl? impl)

public bool SupportsRunLoops => _controlledImpl != null;

internal bool IsSta { get; }

/// <summary>
/// Checks that the current thread is the UI thread.
/// </summary>
Expand Down
136 changes: 73 additions & 63 deletions src/Avalonia.Base/Threading/DispatcherTimer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public partial class DispatcherTimer
public class DispatcherTimer
{
internal static int ActiveTimersCount { get; private set; }

/// <summary>
/// Creates a timer that uses theUI thread's Dispatcher2 to
/// process the timer event at background priority.
/// Creates a timer that uses <see cref="Avalonia.Threading.Dispatcher.CurrentDispatcher"/> to
/// process the timer event at background priority.
/// </summary>
public DispatcherTimer() : this(DispatcherPriority.Background)
public DispatcherTimer()
: this(TimeSpan.Zero, DispatcherPriority.Background, Dispatcher.CurrentDispatcher)
{
}

/// <summary>
/// Creates a timer that uses the UI thread's Dispatcher2 to
/// process the timer event at the specified priority.
/// Creates a timer that uses <see cref="Avalonia.Threading.Dispatcher.CurrentDispatcher"/> to
/// process the timer event at the specified priority.
/// </summary>
/// <param name="priority">
/// The priority to process the timer at.
/// </param>
public DispatcherTimer(DispatcherPriority priority) : this(Threading.Dispatcher.UIThread, priority,
TimeSpan.FromMilliseconds(0))
/// <param name="priority">The priority to process the timer at.</param>
public DispatcherTimer(DispatcherPriority priority)
: this(TimeSpan.Zero, priority, Dispatcher.CurrentDispatcher)
{
}

/// <summary>
/// Creates a timer that uses the specified Dispatcher2 to
/// process the timer event at the specified priority.
/// Creates a timer that uses the specified <see cref="Avalonia.Threading.Dispatcher"/> to
/// process the timer event at the specified priority.
/// </summary>
/// <param name="priority">
/// The priority to process the timer at.
/// </param>
/// <param name="dispatcher">
/// The dispatcher to use to process the timer.
/// </param>
internal DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher) : this(dispatcher, priority,
TimeSpan.FromMilliseconds(0))
/// <param name="priority">The priority to process the timer at.</param>
/// <param name="dispatcher">The dispatcher to use to process the timer.</param>
public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher)
: this(TimeSpan.Zero, priority, dispatcher)
{
}

/// <summary>
/// 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 <see cref="Avalonia.Threading.Dispatcher"/> to
/// process the timer event at the specified priority after the specified timeout.
/// </summary>
/// <param name="interval">
/// The interval to tick the timer after.
/// </param>
/// <param name="priority">
/// The priority to process the timer at.
/// </param>
/// <param name="callback">
/// The callback to call when the timer ticks.
/// </param>
public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback)
: this(Threading.Dispatcher.UIThread, priority, interval)
/// <param name="interval">The interval to tick the timer after.</param>
/// <param name="priority">The priority to process the timer at.</param>
/// <param name="dispatcher">The dispatcher to use to process the timer.</param>
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;
}

/// <summary>
/// Creates a timer that uses <see cref="Avalonia.Threading.Dispatcher.CurrentDispatcher"/> to
/// process the timer event at the specified priority after the specified timeout and with
/// the specified handler.
/// </summary>
/// <param name="interval">The interval to tick the timer after.</param>
/// <param name="priority">The priority to process the timer at.</param>
/// <param name="callback">The callback to call when the timer ticks.</param>
/// <remarks>This constructor immediately starts the timer.</remarks>
public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback)
: this(interval, priority, Dispatcher.CurrentDispatcher, callback)
{
}

/// <summary>
/// Creates a timer that uses the specified <see cref="Avalonia.Threading.Dispatcher"/> to
/// process the timer event at the specified priority after the specified timeout and with
/// the specified handler.
/// </summary>
/// <param name="interval">The interval to tick the timer after.</param>
/// <param name="priority">The priority to process the timer at.</param>
/// <param name="dispatcher">The dispatcher to use to process the timer.</param>
/// <param name="callback">The callback to call when the timer ticks.</param>
/// <remarks>This constructor immediately starts the timer.</remarks>
public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, Dispatcher dispatcher, EventHandler callback)
: this(interval, priority, dispatcher)
{
ArgumentNullException.ThrowIfNull(callback);

Tick += callback;
Start();
Expand Down Expand Up @@ -252,33 +289,6 @@ public static IDisposable RunOnce(
/// </summary>
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)
Expand Down