From 13bbdc729e026a7c4e6ec8ce33f5d83c9f24648f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 19 Mar 2023 19:52:17 +0600 Subject: [PATCH] Use deadline-based and platform-implemented background processing for macOS --- .../src/OSX/platformthreading.mm | 67 ++++++++++++++----- .../Threading/Dispatcher.Queue.cs | 27 +++++--- src/Avalonia.Base/Threading/Dispatcher.cs | 4 ++ .../Threading/IDispatcherImpl.cs | 12 +++- src/Avalonia.Native/DispatcherImpl.cs | 10 ++- src/Avalonia.Native/avn.idl | 3 +- 6 files changed, 93 insertions(+), 30 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index d2d7a365a6..d80df68fea 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -66,25 +66,44 @@ static double distantFutureInterval = (double)50*365*24*3600; ComPtr _events; bool _wakeupDelegateSent; bool _signaled; + bool _backgroundProcessingRequested; CFRunLoopObserverRef _observer; CFRunLoopTimerRef _timer; } +- (void) checkSignaled +{ + bool signaled; + @synchronized (self) { + signaled = _signaled; + _signaled = false; + } + if(signaled) + { + _events->Signaled(); + } +} + - (Signaler*) init { _observer = CFRunLoopObserverCreateWithHandler(nil, - kCFRunLoopBeforeSources | kCFRunLoopAfterWaiting, + kCFRunLoopBeforeSources + | kCFRunLoopAfterWaiting + | kCFRunLoopBeforeWaiting + , true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - bool signaled; - @synchronized (self) { - signaled = self->_signaled; - self->_signaled = false; - } - if(signaled) + if(activity == kCFRunLoopBeforeWaiting) { - self->_events->Signaled(); + bool triggerProcessing; + @synchronized (self) { + triggerProcessing = self->_backgroundProcessingRequested; + self->_backgroundProcessingRequested = false; + } + if(triggerProcessing) + self->_events->ReadyForBackgroundProcessing(); } + [self checkSignaled]; }); CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes); @@ -135,10 +154,27 @@ static double distantFutureInterval = (double)50*365*24*3600; if(_signaled) return; _signaled = true; + dispatch_async(dispatch_get_main_queue(), ^{ + [self checkSignaled]; + }); CFRunLoopWakeUp(CFRunLoopGetMain()); } } +- (void) requestBackgroundProcessing +{ + @synchronized (self) { + if(_backgroundProcessingRequested) + return; + _backgroundProcessingRequested = true; + dispatch_async(dispatch_get_main_queue(), ^{ + // This is needed to wakeup the loop if we are called from inside of BeforeWait hook + }); + } + + +} + @end @@ -165,15 +201,7 @@ public: return [NSThread isMainThread]; }; - bool HasPendingInput() override - { - auto event = [NSApp - nextEventMatchingMask: NSEventMaskAny - untilDate:nil - inMode:NSDefaultRunLoopMode - dequeue:false]; - return event != nil; - }; + void SetEvents(IAvnPlatformThreadingInterfaceEvents *cb) override { @@ -227,6 +255,11 @@ public: [_signaler updateTimer:ms]; }; + void RequestBackgroundProcessing() override { + [_signaler requestBackgroundProcessing]; + } + + }; extern IAvnPlatformThreadingInterface* CreatePlatformThreading() diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs index 0c5414e6d1..105019f277 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs @@ -7,13 +7,21 @@ public partial class Dispatcher { private readonly DispatcherPriorityQueue _queue = new(); private bool _signaled; + private bool _explicitBackgroundProcessingRequested; private const int MaximumTimeProcessingBackgroundJobs = 50; void RequestBackgroundProcessing() { lock (InstanceLock) { - if (_dueTimeForBackgroundProcessing == null) + if (_backgroundProcessingImpl != null) + { + if(_explicitBackgroundProcessingRequested) + return; + _explicitBackgroundProcessingRequested = true; + _backgroundProcessingImpl.RequestBackgroundProcessing(); + } + else if (_dueTimeForBackgroundProcessing == null) { _dueTimeForBackgroundProcessing = Clock.TickCount + 1; UpdateOSTimer(); @@ -21,6 +29,15 @@ public partial class Dispatcher } } + private void OnReadyForExplicitBackgroundProcessing() + { + lock (InstanceLock) + { + _explicitBackgroundProcessingRequested = false; + ExecuteJobsCore(); + } + } + /// /// Force-runs all dispatcher operations ignoring any pending OS events, use with caution /// @@ -124,13 +141,7 @@ public partial class Dispatcher else if (_pendingInputImpl?.CanQueryPendingInput == true) { if (!_pendingInputImpl.HasPendingInput) - { - // On platforms like macOS HasPendingInput check might trigger a timer - // which would result in reentrancy here, so we check if the job - // hasn't been executed yet - if (job.Status == DispatcherOperationStatus.Pending) - ExecuteJob(job); - } + ExecuteJob(job); else { RequestBackgroundProcessing(); diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index 9a0dacfbb8..d1bd15e286 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -23,6 +23,7 @@ public partial class Dispatcher : IDispatcher private IControlledDispatcherImpl? _controlledImpl; private static Dispatcher? s_uiThread; private IDispatcherImplWithPendingInput? _pendingInputImpl; + private IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl; internal Dispatcher(IDispatcherImpl impl, IDispatcherClock clock) { @@ -32,6 +33,9 @@ public partial class Dispatcher : IDispatcher impl.Signaled += Signaled; _controlledImpl = _impl as IControlledDispatcherImpl; _pendingInputImpl = _impl as IDispatcherImplWithPendingInput; + _backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing; + if (_backgroundProcessingImpl != null) + _backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing; } public static Dispatcher UIThread => s_uiThread ??= CreateUIThreadDispatcher(); diff --git a/src/Avalonia.Base/Threading/IDispatcherImpl.cs b/src/Avalonia.Base/Threading/IDispatcherImpl.cs index 7f1e352b51..2cc06d1986 100644 --- a/src/Avalonia.Base/Threading/IDispatcherImpl.cs +++ b/src/Avalonia.Base/Threading/IDispatcherImpl.cs @@ -1,9 +1,11 @@ using System; using System.Threading; +using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.Threading; +[Unstable] public interface IDispatcherImpl { bool CurrentThreadIsLoopThread { get; } @@ -15,7 +17,7 @@ public interface IDispatcherImpl void UpdateTimer(int? dueTimeInMs); } - +[Unstable] public interface IDispatcherImplWithPendingInput : IDispatcherImpl { // Checks if dispatcher implementation can @@ -24,6 +26,14 @@ public interface IDispatcherImplWithPendingInput : IDispatcherImpl bool HasPendingInput { get; } } +[Unstable] +public interface IDispatcherImplWithExplicitBackgroundProcessing : IDispatcherImpl +{ + event Action ReadyForBackgroundProcessing; + void RequestBackgroundProcessing(); +} + +[Unstable] public interface IControlledDispatcherImpl : IDispatcherImplWithPendingInput { // Runs the event loop diff --git a/src/Avalonia.Native/DispatcherImpl.cs b/src/Avalonia.Native/DispatcherImpl.cs index a9e5e6deb1..b1d3cb59de 100644 --- a/src/Avalonia.Native/DispatcherImpl.cs +++ b/src/Avalonia.Native/DispatcherImpl.cs @@ -10,7 +10,7 @@ using MicroCom.Runtime; namespace Avalonia.Native; -internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock +internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock, IDispatcherImplWithExplicitBackgroundProcessing { private readonly IAvnPlatformThreadingInterface _native; private Thread? _loopThread; @@ -25,6 +25,7 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock public event Action Signaled; public event Action Timer; + public event Action ReadyForBackgroundProcessing; private class Events : NativeCallbackBase, IAvnPlatformThreadingInterfaceEvents { @@ -37,6 +38,8 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock public void Signaled() => _parent.Signaled?.Invoke(); public void Timer() => _parent.Timer?.Invoke(); + + public void ReadyForBackgroundProcessing() => _parent.ReadyForBackgroundProcessing?.Invoke(); } public bool CurrentThreadIsLoopThread @@ -60,8 +63,8 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock _native.UpdateTimer(ms); } - public bool CanQueryPendingInput => true; - public bool HasPendingInput => _native.HasPendingInput() != 0; + public bool CanQueryPendingInput => false; + public bool HasPendingInput => false; class RunLoopFrame : IDisposable { @@ -124,4 +127,5 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock frame.Exception = capture; frame.CancellationTokenSource.Cancel(); } + public void RequestBackgroundProcessing() => _native.RequestBackgroundProcessing(); } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 7763d0d2fc..09e9168d8f 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -649,6 +649,7 @@ interface IAvnPlatformThreadingInterfaceEvents : IUnknown { void Signaled(); void Timer(); + void ReadyForBackgroundProcessing(); } [uuid(97330f88-c22b-4a8e-a130-201520091b01)] @@ -661,12 +662,12 @@ interface IAvnLoopCancellation : IUnknown interface IAvnPlatformThreadingInterface : IUnknown { bool GetCurrentThreadIsLoopThread(); - bool HasPendingInput(); void SetEvents(IAvnPlatformThreadingInterfaceEvents* cb); IAvnLoopCancellation* CreateLoopCancellation(); void RunLoop(IAvnLoopCancellation* cancel); void Signal(); void UpdateTimer(int ms); + void RequestBackgroundProcessing(); } [uuid(6c621a6e-e4c1-4ae3-9749-83eeeffa09b6)]