Browse Source

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

pull/10691/head
Nikita Tsukanov 3 years ago
parent
commit
13bbdc729e
  1. 67
      native/Avalonia.Native/src/OSX/platformthreading.mm
  2. 27
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  3. 4
      src/Avalonia.Base/Threading/Dispatcher.cs
  4. 12
      src/Avalonia.Base/Threading/IDispatcherImpl.cs
  5. 10
      src/Avalonia.Native/DispatcherImpl.cs
  6. 3
      src/Avalonia.Native/avn.idl

67
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()

27
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
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();

12
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

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

3
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)]

Loading…
Cancel
Save