Browse Source

Merge branch 'master' into MikeCodesDotNET-readme-update

pull/11213/head
Mike James 3 years ago
committed by GitHub
parent
commit
d4d0202701
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  2. 57
      src/Avalonia.Base/Threading/DispatcherFrame.cs
  3. 6
      src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
  4. 44
      tests/Avalonia.Base.UnitTests/DispatcherTests.cs

42
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -248,11 +248,11 @@ public partial class Dispatcher
/// An operation representing the queued delegate to be invoked.
/// </returns>
/// <remarks>
/// Note that the default priority is DispatcherPriority.Normal.
/// Note that the default priority is DispatcherPriority.Default.
/// </remarks>
public DispatcherOperation InvokeAsync(Action callback)
{
return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None);
return InvokeAsync(callback, default, CancellationToken.None);
}
/// <summary>
@ -326,11 +326,11 @@ public partial class Dispatcher
/// An operation representing the queued delegate to be invoked.
/// </returns>
/// <remarks>
/// Note that the default priority is DispatcherPriority.Normal.
/// Note that the default priority is DispatcherPriority.Default.
/// </remarks>
public DispatcherOperation<TResult> InvokeAsync<TResult>(Func<TResult> callback)
{
return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None);
return InvokeAsync(callback, DispatcherPriority.Default, CancellationToken.None);
}
/// <summary>
@ -541,6 +541,18 @@ public partial class Dispatcher
InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None);
}
/// <summary>
/// Executes the specified Func&lt;Task&gt; asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="callback">
/// A Func&lt;Task&gt; delegate to invoke through the dispatcher.
/// </param>
/// <returns>
/// An task that completes after the task returned from callback finishes.
/// </returns>
public Task InvokeAsync(Func<Task> callback) => InvokeAsync(callback, DispatcherPriority.Default);
/// <summary>
/// Executes the specified Func&lt;Task&gt; asynchronously on the
/// thread that the Dispatcher was created on
@ -556,11 +568,29 @@ public partial class Dispatcher
/// <returns>
/// An task that completes after the task returned from callback finishes
/// </returns>
public Task InvokeAsync(Func<Task> callback, DispatcherPriority priority = default)
public Task InvokeAsync(Func<Task> callback, DispatcherPriority priority)
{
_ = callback ?? throw new ArgumentNullException(nameof(callback));
return InvokeAsync<Task>(callback, priority).GetTask().Unwrap();
}
/// <summary>
/// Executes the specified Func&lt;Task&lt;TResult&gt;&gt; asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="action">
/// A Func&lt;Task&lt;TResult&gt;&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
/// callback is invoked relative to the other pending operations
/// in the Dispatcher.
/// </param>
/// <returns>
/// An task that completes after the task returned from callback finishes
/// </returns>
public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> action) =>
InvokeAsync(action, DispatcherPriority.Default);
/// <summary>
/// Executes the specified Func&lt;Task&lt;TResult&gt;&gt; asynchronously on the
@ -577,7 +607,7 @@ public partial class Dispatcher
/// <returns>
/// An task that completes after the task returned from callback finishes
/// </returns>
public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> action, DispatcherPriority priority = default)
public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> action, DispatcherPriority priority)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
return InvokeAsync<Task<TResult>>(action, priority).GetTask().Unwrap();

57
src/Avalonia.Base/Threading/DispatcherFrame.cs

@ -91,31 +91,44 @@ public class DispatcherFrame
internal void Run(IControlledDispatcherImpl impl)
{
// Since the actual platform run loop is controlled by a Cancellation token, we are restarting
// it if frame still needs to run
while (Continue)
RunCore(impl);
}
private void RunCore(IControlledDispatcherImpl impl)
{
if (_isRunning)
throw new InvalidOperationException("This frame is already running");
_isRunning = true;
try
{
_cancellationTokenSource = new CancellationTokenSource();
// Wake up the dispatcher in case it has pending jobs
Dispatcher.RequestProcessing();
impl.RunLoop(_cancellationTokenSource.Token);
}
finally
Dispatcher.VerifyAccess();
// Since the actual platform run loop is controlled by a Cancellation token, we have an
// outer loop that restarts the platform one in case Continue was set to true after being set to false
while (true)
{
_isRunning = false;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = null;
// Take the instance lock since `Continue` is changed from one too
lock (Dispatcher.InstanceLock)
{
if (!Continue)
return;
if (_isRunning)
throw new InvalidOperationException("This frame is already running");
_cancellationTokenSource = new CancellationTokenSource();
_isRunning = true;
}
try
{
// Wake up the dispatcher in case it has pending jobs
Dispatcher.RequestProcessing();
impl.RunLoop(_cancellationTokenSource.Token);
}
finally
{
lock (Dispatcher.InstanceLock)
{
_isRunning = false;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
}
}
}
internal void MaybeExitOnDispatcherRequest()
{

6
src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs

@ -58,6 +58,10 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
public void RunLoop(CancellationToken token)
{
CancellationTokenRegistration registration = default;
if (token.CanBeCanceled)
registration = token.Register(() => _wakeup.Set());
while (!token.IsCancellationRequested)
{
bool signaled;
@ -105,5 +109,7 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
else
_wakeup.WaitOne();
}
registration.Dispose();
}
}

44
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<int> 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();
}
}
}
Loading…
Cancel
Save