浏览代码

Use deadline-based and platform-implemented background processing for macOS

Nikita Tsukanov 2 年之前
父节点
当前提交
13bbdc729e

+ 50 - 17
native/Avalonia.Native/src/OSX/platformthreading.mm

@@ -66,25 +66,44 @@ static double distantFutureInterval = (double)50*365*24*3600;
     ComPtr<IAvnPlatformThreadingInterfaceEvents> _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()

+ 19 - 8
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();
+        }
+    }
+
     /// <summary>
     /// Force-runs all dispatcher operations ignoring any pending OS events, use with caution
     /// </summary>
@@ -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();

+ 4 - 0
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();

+ 11 - 1
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

+ 7 - 3
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();
 }

+ 2 - 1
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)]