diff --git a/src/Avalonia.Base/Threading/DispatcherFrame.cs b/src/Avalonia.Base/Threading/DispatcherFrame.cs index 1f8974dfa3..e826432475 100644 --- a/src/Avalonia.Base/Threading/DispatcherFrame.cs +++ b/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() { diff --git a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs b/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs index 20aa91c83e..fdc098777a 100644 --- a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs +++ b/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(); } } \ No newline at end of file