Browse Source

Properly check if operation is pending when executing/aborting (#19132)

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
release/11.3.3
Nikita Tsukanov 7 months ago
committed by Julien Lebosquain
parent
commit
b8263d823c
  1. 3
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  2. 16
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  3. 33
      src/Avalonia.Base/Threading/DispatcherOperation.cs

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

@ -445,7 +445,8 @@ public partial class Dispatcher
// so it is safe to modify the operation outside of the lock. // so it is safe to modify the operation outside of the lock.
// Just mark the operation as aborted, which we can safely // Just mark the operation as aborted, which we can safely
// return to the user. // return to the user.
operation.DoAbort(); operation.Status = DispatcherOperationStatus.Aborted;
operation.CallAbortCallbacks();
} }
} }

16
src/Avalonia.Base/Threading/Dispatcher.Queue.cs

@ -134,7 +134,14 @@ public partial class Dispatcher
private void ExecuteJob(DispatcherOperation job) private void ExecuteJob(DispatcherOperation job)
{ {
lock (InstanceLock) lock (InstanceLock)
{
if(job.Status != DispatcherOperationStatus.Pending)
return;
_queue.RemoveItem(job); _queue.RemoveItem(job);
job.Status = DispatcherOperationStatus.Executing;
}
job.Execute(); job.Execute();
// The backend might be firing timers with a low priority, // The backend might be firing timers with a low priority,
// so we manually check if our high priority timers are due for execution // so we manually check if our high priority timers are due for execution
@ -236,11 +243,16 @@ public partial class Dispatcher
} }
} }
internal void Abort(DispatcherOperation operation) internal bool Abort(DispatcherOperation operation)
{ {
lock (InstanceLock) lock (InstanceLock)
{
if (operation.Status != DispatcherOperationStatus.Pending)
return false;
_queue.RemoveItem(operation); _queue.RemoveItem(operation);
operation.DoAbort(); operation.Status = DispatcherOperationStatus.Aborted;
}
return true;
} }
// Returns whether or not the priority was set. // Returns whether or not the priority was set.

33
src/Avalonia.Base/Threading/DispatcherOperation.cs

@ -11,7 +11,7 @@ namespace Avalonia.Threading;
public class DispatcherOperation public class DispatcherOperation
{ {
protected readonly bool ThrowOnUiThread; protected readonly bool ThrowOnUiThread;
public DispatcherOperationStatus Status { get; protected set; } public DispatcherOperationStatus Status { get; internal set; }
public Dispatcher Dispatcher { get; } public Dispatcher Dispatcher { get; }
public DispatcherPriority Priority public DispatcherPriority Priority
@ -115,13 +115,13 @@ public class DispatcherOperation
public bool Abort() public bool Abort()
{ {
lock (Dispatcher.InstanceLock) if (Dispatcher.Abort(this))
{ {
if (Status != DispatcherOperationStatus.Pending) CallAbortCallbacks();
return false;
Dispatcher.Abort(this);
return true; return true;
} }
return false;
} }
/// <summary> /// <summary>
@ -254,20 +254,15 @@ public class DispatcherOperation
return GetTask().GetAwaiter(); return GetTask().GetAwaiter();
} }
internal void DoAbort() internal void CallAbortCallbacks()
{ {
Status = DispatcherOperationStatus.Aborted;
AbortTask(); AbortTask();
_aborted?.Invoke(this, EventArgs.Empty); _aborted?.Invoke(this, EventArgs.Empty);
} }
internal void Execute() internal void Execute()
{ {
lock (Dispatcher.InstanceLock) Debug.Assert(Status == DispatcherOperationStatus.Executing);
{
Status = DispatcherOperationStatus.Executing;
}
try try
{ {
using (AvaloniaSynchronizationContext.Ensure(Dispatcher, Priority)) using (AvaloniaSynchronizationContext.Ensure(Dispatcher, Priority))
@ -311,7 +306,19 @@ public class DispatcherOperation
internal virtual object? GetResult() => null; internal virtual object? GetResult() => null;
protected virtual void AbortTask() => (TaskSource as TaskCompletionSource<object?>)?.SetCanceled(); protected virtual void AbortTask()
{
object? taskSource;
lock (Dispatcher.InstanceLock)
{
Debug.Assert(Status == DispatcherOperationStatus.Aborted);
// There is no way for TaskSource to become not-null after being null with aborted tasks,
// so it's safe to save it here and use after exiting the lock
taskSource = TaskSource;
}
(taskSource as TaskCompletionSource<object?>)?.SetCanceled();
}
private static CancellationToken CreateCancelledToken() private static CancellationToken CreateCancelledToken()
{ {

Loading…
Cancel
Save