diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 347e6b3b08..8d64cb2a82 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -109,6 +109,42 @@
baseline/netstandard2.0/Avalonia.Base.dll
target/netstandard2.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.get_IsCompleted
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.GetAwaiter
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.GetResult
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.OnCompleted(System.Action)
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable`1.GetAwaiter
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable`1.GetResult
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Controls.Primitives.IPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
@@ -187,6 +223,30 @@
baseline/netstandard2.0/Avalonia.Controls.dll
target/netstandard2.0/Avalonia.Controls.dll
+
+ CP0007
+ T:Avalonia.Threading.DispatcherPriorityAwaitable
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0007
+ T:Avalonia.Threading.DispatcherPriorityAwaitable`1
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Threading.DispatcherPriorityAwaitable
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Threading.DispatcherPriorityAwaitable`1
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0009
T:Avalonia.Diagnostics.StyleDiagnostics
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
index 324a50e4b4..afee481252 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
@@ -672,4 +672,70 @@ public partial class Dispatcher
///
public DispatcherPriorityAwaitable AwaitWithPriority(Task task, DispatcherPriority priority) =>
new(this, task, priority);
+
+ ///
+ /// Creates an awaitable object that asynchronously resumes execution on the dispatcher.
+ ///
+ ///
+ /// An awaitable object that asynchronously resumes execution on the dispatcher.
+ ///
+ ///
+ /// This method is equivalent to calling the method
+ /// and passing in .
+ ///
+ public DispatcherPriorityAwaitable Resume() =>
+ Resume(DispatcherPriority.Background);
+
+ /// ``
+ /// Creates an awaitable object that asynchronously resumes execution on the dispatcher. The work that occurs
+ /// when control returns to the code awaiting the result of this method is scheduled with the specified priority.
+ ///
+ /// The priority at which to schedule the continuation.
+ ///
+ /// An awaitable object that asynchronously resumes execution on the dispatcher.
+ ///
+ public DispatcherPriorityAwaitable Resume(DispatcherPriority priority)
+ {
+ DispatcherPriority.Validate(priority, nameof(priority));
+ return new(this, null, priority);
+ }
+
+ ///
+ /// Creates an awaitable object that asynchronously yields control back to the current dispatcher
+ /// and provides an opportunity for the dispatcher to process other events.
+ ///
+ ///
+ /// An awaitable object that asynchronously yields control back to the current dispatcher
+ /// and provides an opportunity for the dispatcher to process other events.
+ ///
+ ///
+ /// This method is equivalent to calling the method
+ /// and passing in .
+ ///
+ ///
+ /// The current thread is not the UI thread.
+ ///
+ public static DispatcherPriorityAwaitable Yield() =>
+ Yield(DispatcherPriority.Background);
+
+ ///
+ /// Creates an cawaitable object that asynchronously yields control back to the current dispatcher
+ /// and provides an opportunity for the dispatcher to process other events. The work that occurs when
+ /// control returns to the code awaiting the result of this method is scheduled with the specified priority.
+ ///
+ /// The priority at which to schedule the continuation.
+ ///
+ /// An awaitable object that asynchronously yields control back to the current dispatcher
+ /// and provides an opportunity for the dispatcher to process other events.
+ ///
+ ///
+ /// The current thread is not the UI thread.
+ ///
+ public static DispatcherPriorityAwaitable Yield(DispatcherPriority priority)
+ {
+ // TODO12: Update to use Dispatcher.CurrentDispatcher once multi-dispatcher support is merged
+ var current = UIThread;
+ current.VerifyAccess();
+ return UIThread.Resume(priority);
+ }
}
diff --git a/src/Avalonia.Base/Threading/DispatcherPriorityAwaitable.cs b/src/Avalonia.Base/Threading/DispatcherPriorityAwaitable.cs
index 456e2d7551..ab4fb38b5a 100644
--- a/src/Avalonia.Base/Threading/DispatcherPriorityAwaitable.cs
+++ b/src/Avalonia.Base/Threading/DispatcherPriorityAwaitable.cs
@@ -1,40 +1,130 @@
using System;
using System.Runtime.CompilerServices;
+using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Threading;
-public class DispatcherPriorityAwaitable : INotifyCompletion
+///
+/// A simple awaitable type that will return a DispatcherPriorityAwaiter.
+///
+public struct DispatcherPriorityAwaitable
{
private readonly Dispatcher _dispatcher;
- private protected readonly Task Task;
+ private readonly Task? _task;
private readonly DispatcherPriority _priority;
- internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task task, DispatcherPriority priority)
+ internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task? task, DispatcherPriority priority)
{
_dispatcher = dispatcher;
- Task = task;
+ _task = task;
_priority = priority;
}
-
- public void OnCompleted(Action continuation) =>
- Task.ContinueWith(_ => _dispatcher.Post(continuation, _priority));
- public bool IsCompleted => Task.IsCompleted;
+ public DispatcherPriorityAwaiter GetAwaiter() => new(_dispatcher, _task, _priority);
+}
+
+///
+/// A simple awaiter type that will queue the continuation to a dispatcher at a specific priority.
+///
+///
+/// This is returned from DispatcherPriorityAwaitable.GetAwaiter()
+///
+public struct DispatcherPriorityAwaiter : INotifyCompletion
+{
+ private readonly Dispatcher _dispatcher;
+ private readonly Task? _task;
+ private readonly DispatcherPriority _priority;
+
+ internal DispatcherPriorityAwaiter(Dispatcher dispatcher, Task? task, DispatcherPriority priority)
+ {
+ _dispatcher = dispatcher;
+ _task = task;
+ _priority = priority;
+ }
+
+ public void OnCompleted(Action continuation)
+ {
+ if(_task == null || _task.IsCompleted)
+ _dispatcher.Post(continuation, _priority);
+ else
+ {
+ var self = this;
+ _task.ConfigureAwait(false).GetAwaiter().OnCompleted(() =>
+ {
+ self._dispatcher.Post(continuation, self._priority);
+ });
+ }
+ }
+
+ ///
+ /// This always returns false since continuation is requested to be queued to a dispatcher queue
+ ///
+ public bool IsCompleted => false;
+
+ public void GetResult()
+ {
+ if (_task != null)
+ _task.GetAwaiter().GetResult();
+ }
+}
+
+///
+/// A simple awaitable type that will return a DispatcherPriorityAwaiter<T>.
+///
+public struct DispatcherPriorityAwaitable
+{
+ private readonly Dispatcher _dispatcher;
+ private readonly Task _task;
+ private readonly DispatcherPriority _priority;
- public void GetResult() => Task.GetAwaiter().GetResult();
+ internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task task, DispatcherPriority priority)
+ {
+ _dispatcher = dispatcher;
+ _task = task;
+ _priority = priority;
+ }
- public DispatcherPriorityAwaitable GetAwaiter() => this;
+ public DispatcherPriorityAwaiter GetAwaiter() => new(_dispatcher, _task, _priority);
}
-public sealed class DispatcherPriorityAwaitable : DispatcherPriorityAwaitable
+///
+/// A simple awaiter type that will queue the continuation to a dispatcher at a specific priority.
+///
+///
+/// This is returned from DispatcherPriorityAwaitable<T>.GetAwaiter()
+///
+public struct DispatcherPriorityAwaiter : INotifyCompletion
{
- internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task task, DispatcherPriority priority) : base(
- dispatcher, task, priority)
+ private readonly Dispatcher _dispatcher;
+ private readonly Task _task;
+ private readonly DispatcherPriority _priority;
+
+ internal DispatcherPriorityAwaiter(Dispatcher dispatcher, Task task, DispatcherPriority priority)
{
+ _dispatcher = dispatcher;
+ _task = task;
+ _priority = priority;
}
- public new T GetResult() => ((Task)Task).GetAwaiter().GetResult();
+ public void OnCompleted(Action continuation)
+ {
+ if(_task.IsCompleted)
+ _dispatcher.Post(continuation, _priority);
+ else
+ {
+ var self = this;
+ _task.ConfigureAwait(false).GetAwaiter().OnCompleted(() =>
+ {
+ self._dispatcher.Post(continuation, self._priority);
+ });
+ }
+ }
+
+ ///
+ /// This always returns false since continuation is requested to be queued to a dispatcher queue
+ ///
+ public bool IsCompleted => false;
- public new DispatcherPriorityAwaitable GetAwaiter() => this;
-}
+ public void GetResult() => _task.GetAwaiter().GetResult();
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs
index a667057708..1884a1ab65 100644
--- a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs
+++ b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs
@@ -505,4 +505,104 @@ public partial class DispatcherTests
t.GetAwaiter().GetResult();
}
}
+
+
+ [Fact]
+ public async Task DispatcherResumeContinuesOnUIThread()
+ {
+ using var services = new DispatcherServices(new SimpleControlledDispatcherImpl());
+
+ var tokenSource = new CancellationTokenSource();
+ var workload = Dispatcher.UIThread.InvokeAsync(
+ async () =>
+ {
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+
+ await Task.Delay(1).ConfigureAwait(false);
+ Assert.False(Dispatcher.UIThread.CheckAccess());
+
+ await Dispatcher.UIThread.Resume();
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+
+ tokenSource.Cancel();
+ });
+
+ Dispatcher.UIThread.MainLoop(tokenSource.Token);
+ }
+
+ [Fact]
+ public async Task DispatcherYieldContinuesOnUIThread()
+ {
+ using var services = new DispatcherServices(new SimpleControlledDispatcherImpl());
+
+ var tokenSource = new CancellationTokenSource();
+ var workload = Dispatcher.UIThread.InvokeAsync(
+ async () =>
+ {
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+
+ await Dispatcher.Yield();
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+
+ tokenSource.Cancel();
+ });
+
+ Dispatcher.UIThread.MainLoop(tokenSource.Token);
+ }
+
+ [Fact]
+ public async Task DispatcherYieldThrowsOnNonUIThread()
+ {
+ using var services = new DispatcherServices(new SimpleControlledDispatcherImpl());
+
+ var tokenSource = new CancellationTokenSource();
+ var workload = Dispatcher.UIThread.InvokeAsync(
+ async () =>
+ {
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+
+ await Task.Delay(1).ConfigureAwait(false);
+ Assert.False(Dispatcher.UIThread.CheckAccess());
+ await Assert.ThrowsAsync(async () => await Dispatcher.Yield());
+
+ tokenSource.Cancel();
+ });
+
+ Dispatcher.UIThread.MainLoop(tokenSource.Token);
+ }
+
+ [Fact]
+ public async Task AwaitWithPriorityRunsOnUIThread()
+ {
+ static async Task Workload()
+ {
+ await Task.Delay(1).ConfigureAwait(false);
+ Assert.False(Dispatcher.UIThread.CheckAccess());
+
+ return Thread.CurrentThread.ManagedThreadId;
+ }
+
+ using var services = new DispatcherServices(new SimpleControlledDispatcherImpl());
+
+ var tokenSource = new CancellationTokenSource();
+ var workload = Dispatcher.UIThread.InvokeAsync(
+ async () =>
+ {
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+ Task taskWithoutResult = Workload();
+
+ await Dispatcher.UIThread.AwaitWithPriority(taskWithoutResult, DispatcherPriority.Default);
+
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+ Task taskWithResult = Workload();
+
+ await Dispatcher.UIThread.AwaitWithPriority(taskWithResult, DispatcherPriority.Default);
+
+ Assert.True(Dispatcher.UIThread.CheckAccess());
+
+ tokenSource.Cancel();
+ });
+
+ Dispatcher.UIThread.MainLoop(tokenSource.Token);
+ }
}