From 015768c5bd29932372a5007a087e2d4f11eaa238 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 3 May 2023 14:26:02 +0600 Subject: [PATCH] Make sure that Dispatcher.InvokeAsync unwraps tasks and that correct overload is being chosen --- .../Threading/Dispatcher.Invoke.cs | 42 +++++++++++++++--- .../DispatcherTests.cs | 44 +++++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs index 699186868a..bb1663eac0 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs @@ -248,11 +248,11 @@ public partial class Dispatcher /// An operation representing the queued delegate to be invoked. /// /// - /// Note that the default priority is DispatcherPriority.Normal. + /// Note that the default priority is DispatcherPriority.Default. /// public DispatcherOperation InvokeAsync(Action callback) { - return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); + return InvokeAsync(callback, default, CancellationToken.None); } /// @@ -326,11 +326,11 @@ public partial class Dispatcher /// An operation representing the queued delegate to be invoked. /// /// - /// Note that the default priority is DispatcherPriority.Normal. + /// Note that the default priority is DispatcherPriority.Default. /// public DispatcherOperation InvokeAsync(Func callback) { - return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); + return InvokeAsync(callback, DispatcherPriority.Default, CancellationToken.None); } /// @@ -541,6 +541,18 @@ public partial class Dispatcher InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None); } + /// + /// Executes the specified Func<Task> asynchronously on the + /// thread that the Dispatcher was created on + /// + /// + /// A Func<Task> delegate to invoke through the dispatcher. + /// + /// + /// An task that completes after the task returned from callback finishes. + /// + public Task InvokeAsync(Func callback) => InvokeAsync(callback, DispatcherPriority.Default); + /// /// Executes the specified Func<Task> asynchronously on the /// thread that the Dispatcher was created on @@ -556,11 +568,29 @@ public partial class Dispatcher /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeAsync(Func callback, DispatcherPriority priority = default) + public Task InvokeAsync(Func callback, DispatcherPriority priority) { _ = callback ?? throw new ArgumentNullException(nameof(callback)); return InvokeAsync(callback, priority).GetTask().Unwrap(); } + + /// + /// Executes the specified Func<Task<TResult>> asynchronously on the + /// thread that the Dispatcher was created on + /// + /// + /// A Func<Task<TResult>> delegate to invoke through the dispatcher. + /// + /// + /// The priority that determines in what order the specified + /// callback is invoked relative to the other pending operations + /// in the Dispatcher. + /// + /// + /// An task that completes after the task returned from callback finishes + /// + public Task InvokeAsync(Func> action) => + InvokeAsync(action, DispatcherPriority.Default); /// /// Executes the specified Func<Task<TResult>> asynchronously on the @@ -577,7 +607,7 @@ public partial class Dispatcher /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeAsync(Func> action, DispatcherPriority priority = default) + public Task InvokeAsync(Func> action, DispatcherPriority priority) { _ = action ?? throw new ArgumentNullException(nameof(action)); return InvokeAsync>(action, priority).GetTask().Unwrap(); diff --git a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs index 9ba3f3980d..7b401918ce 100644 --- a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs +++ b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls.Platform; using Avalonia.Threading; using Avalonia.Utilities; using Xunit; @@ -458,4 +460,46 @@ public class DispatcherTests } } + [Fact] + public void DispatcherInvokeAsyncUnwrapsTasks() + { + int asyncMethodStage = 0; + + async Task AsyncMethod() + { + asyncMethodStage = 1; + await Task.Delay(200); + asyncMethodStage = 2; + } + + async Task AsyncMethodWithResult() + { + await Task.Delay(100); + return 1; + } + + async Task Test() + { + await Dispatcher.UIThread.InvokeAsync(AsyncMethod); + Assert.Equal(2, asyncMethodStage); + Assert.Equal(1, await Dispatcher.UIThread.InvokeAsync(AsyncMethodWithResult)); + asyncMethodStage = 0; + + await Dispatcher.UIThread.InvokeAsync(AsyncMethod, DispatcherPriority.Default); + Assert.Equal(2, asyncMethodStage); + Assert.Equal(1, await Dispatcher.UIThread.InvokeAsync(AsyncMethodWithResult, DispatcherPriority.Default)); + + Dispatcher.UIThread.ExitAllFrames(); + } + + using (new DispatcherServices(new ManagedDispatcherImpl(null))) + { + var t = Test(); + var cts = new CancellationTokenSource(); + Task.Delay(3000).ContinueWith(_ => cts.Cancel()); + Dispatcher.UIThread.MainLoop(cts.Token); + Assert.True(t.IsCompletedSuccessfully); + t.GetAwaiter().GetResult(); + } + } } \ No newline at end of file