Browse Source

Refactored dispatcher clock

pull/10691/head
Nikita Tsukanov 3 years ago
parent
commit
dbbdcb95cd
  1. 11
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  2. 18
      src/Avalonia.Base/Threading/Dispatcher.Timers.cs
  3. 6
      src/Avalonia.Base/Threading/Dispatcher.cs
  4. 6
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  5. 13
      src/Avalonia.Base/Threading/IDispatcherClock.cs
  6. 16
      src/Avalonia.Base/Threading/IDispatcherImpl.cs
  7. 3
      src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
  8. 9
      src/Avalonia.Native/DispatcherImpl.cs
  9. 6
      src/Avalonia.X11/X11PlatformThreading.cs
  10. 10
      src/Windows/Avalonia.Win32/Win32DispatcherImpl.cs
  11. 26
      tests/Avalonia.Base.UnitTests/DispatcherTests.cs

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

@ -23,7 +23,7 @@ public partial class Dispatcher
} }
else if (_dueTimeForBackgroundProcessing == null) else if (_dueTimeForBackgroundProcessing == null)
{ {
_dueTimeForBackgroundProcessing = Clock.TickCount + 1; _dueTimeForBackgroundProcessing = Now + 1;
UpdateOSTimer(); UpdateOSTimer();
} }
} }
@ -68,7 +68,8 @@ public partial class Dispatcher
public event Action? Signaled; public event Action? Signaled;
public event Action? Timer; public event Action? Timer;
public void UpdateTimer(int? dueTimeInMs) public long Now => 0;
public void UpdateTimer(long? dueTimeInMs)
{ {
} }
} }
@ -119,7 +120,7 @@ public partial class Dispatcher
void ExecuteJobsCore() void ExecuteJobsCore()
{ {
int? backgroundJobExecutionStartedAt = null; long? backgroundJobExecutionStartedAt = null;
while (true) while (true)
{ {
DispatcherOperation? job; DispatcherOperation? job;
@ -153,9 +154,9 @@ public partial class Dispatcher
else else
{ {
if (backgroundJobExecutionStartedAt == null) if (backgroundJobExecutionStartedAt == null)
backgroundJobExecutionStartedAt = Clock.TickCount; backgroundJobExecutionStartedAt = Now;
if (Clock.TickCount - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs) if (Now - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs)
{ {
_signaled = true; _signaled = true;
RequestBackgroundProcessing(); RequestBackgroundProcessing();

18
src/Avalonia.Base/Threading/Dispatcher.Timers.cs

@ -9,11 +9,13 @@ public partial class Dispatcher
private List<DispatcherTimer> _timers = new(); private List<DispatcherTimer> _timers = new();
private long _timersVersion; private long _timersVersion;
private bool _dueTimeFound; private bool _dueTimeFound;
private int _dueTimeInMs; private long _dueTimeInMs;
private int? _dueTimeForTimers; private long? _dueTimeForTimers;
private int? _dueTimeForBackgroundProcessing; private long? _dueTimeForBackgroundProcessing;
private int? _osTimerSetTo; private long? _osTimerSetTo;
internal long Now => _impl.Now;
private void UpdateOSTimer() private void UpdateOSTimer()
{ {
@ -40,7 +42,7 @@ public partial class Dispatcher
if (!_hasShutdownFinished) // Dispatcher thread, does not technically need the lock to read if (!_hasShutdownFinished) // Dispatcher thread, does not technically need the lock to read
{ {
bool oldDueTimeFound = _dueTimeFound; bool oldDueTimeFound = _dueTimeFound;
int oldDueTimeInTicks = _dueTimeInMs; long oldDueTimeInTicks = _dueTimeInMs;
_dueTimeFound = false; _dueTimeFound = false;
_dueTimeInMs = 0; _dueTimeInMs = 0;
@ -113,11 +115,11 @@ public partial class Dispatcher
lock (InstanceLock) lock (InstanceLock)
{ {
_impl.UpdateTimer(_osTimerSetTo = null); _impl.UpdateTimer(_osTimerSetTo = null);
needToPromoteTimers = _dueTimeForTimers.HasValue && _dueTimeForTimers.Value <= Clock.TickCount; needToPromoteTimers = _dueTimeForTimers.HasValue && _dueTimeForTimers.Value <= Now;
if (needToPromoteTimers) if (needToPromoteTimers)
_dueTimeForTimers = null; _dueTimeForTimers = null;
needToProcessQueue = _dueTimeForBackgroundProcessing.HasValue && needToProcessQueue = _dueTimeForBackgroundProcessing.HasValue &&
_dueTimeForBackgroundProcessing.Value <= Clock.TickCount; _dueTimeForBackgroundProcessing.Value <= Now;
if (needToProcessQueue) if (needToProcessQueue)
_dueTimeForBackgroundProcessing = null; _dueTimeForBackgroundProcessing = null;
} }
@ -131,7 +133,7 @@ public partial class Dispatcher
internal void PromoteTimers() internal void PromoteTimers()
{ {
int currentTimeInTicks = Clock.TickCount; long currentTimeInTicks = Now;
try try
{ {
List<DispatcherTimer>? timers = null; List<DispatcherTimer>? timers = null;

6
src/Avalonia.Base/Threading/Dispatcher.cs

@ -17,7 +17,6 @@ namespace Avalonia.Threading;
public partial class Dispatcher : IDispatcher public partial class Dispatcher : IDispatcher
{ {
private IDispatcherImpl _impl; private IDispatcherImpl _impl;
internal IDispatcherClock Clock { get; }
internal object InstanceLock { get; } = new(); internal object InstanceLock { get; } = new();
private bool _hasShutdownFinished; private bool _hasShutdownFinished;
private IControlledDispatcherImpl? _controlledImpl; private IControlledDispatcherImpl? _controlledImpl;
@ -25,10 +24,9 @@ public partial class Dispatcher : IDispatcher
private IDispatcherImplWithPendingInput? _pendingInputImpl; private IDispatcherImplWithPendingInput? _pendingInputImpl;
private IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl; private IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl;
internal Dispatcher(IDispatcherImpl impl, IDispatcherClock clock) internal Dispatcher(IDispatcherImpl impl)
{ {
_impl = impl; _impl = impl;
Clock = clock;
impl.Timer += OnOSTimer; impl.Timer += OnOSTimer;
impl.Signaled += Signaled; impl.Signaled += Signaled;
_controlledImpl = _impl as IControlledDispatcherImpl; _controlledImpl = _impl as IControlledDispatcherImpl;
@ -51,7 +49,7 @@ public partial class Dispatcher : IDispatcher
else else
impl = new NullDispatcherImpl(); impl = new NullDispatcherImpl();
} }
return new Dispatcher(impl, impl as IDispatcherClock ?? new DefaultDispatcherClock()); return new Dispatcher(impl);
} }
/// <summary> /// <summary>

6
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -125,7 +125,7 @@ public partial class DispatcherTimer
if (_isEnabled) if (_isEnabled)
{ {
DueTimeInMs = _dispatcher.Clock.TickCount + (int)_interval.TotalMilliseconds; DueTimeInMs = _dispatcher.Now + (long)_interval.TotalMilliseconds;
updateOSTimer = true; updateOSTimer = true;
} }
} }
@ -288,7 +288,7 @@ public partial class DispatcherTimer
// BeginInvoke a new operation. // BeginInvoke a new operation.
_operation = _dispatcher.InvokeAsync(FireTick, DispatcherPriority.Inactive); _operation = _dispatcher.InvokeAsync(FireTick, DispatcherPriority.Inactive);
DueTimeInMs = _dispatcher.Clock.TickCount + (int)_interval.TotalMilliseconds; DueTimeInMs = _dispatcher.Now + (long)_interval.TotalMilliseconds;
if (_interval.TotalMilliseconds == 0 && _dispatcher.CheckAccess()) if (_interval.TotalMilliseconds == 0 && _dispatcher.CheckAccess())
{ {
@ -348,5 +348,5 @@ public partial class DispatcherTimer
private bool _isEnabled; private bool _isEnabled;
// used by Dispatcher // used by Dispatcher
internal int DueTimeInMs { get; private set; } internal long DueTimeInMs { get; private set; }
} }

13
src/Avalonia.Base/Threading/IDispatcherClock.cs

@ -1,13 +0,0 @@
using System;
namespace Avalonia.Threading;
internal interface IDispatcherClock
{
int TickCount { get; }
}
internal class DefaultDispatcherClock : IDispatcherClock
{
public int TickCount => Environment.TickCount;
}

16
src/Avalonia.Base/Threading/IDispatcherImpl.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Threading; using System.Threading;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
@ -14,7 +15,8 @@ public interface IDispatcherImpl
void Signal(); void Signal();
event Action Signaled; event Action Signaled;
event Action Timer; event Action Timer;
void UpdateTimer(int? dueTimeInMs); long Now { get; }
void UpdateTimer(long? dueTimeInMs);
} }
[Unstable] [Unstable]
@ -40,10 +42,11 @@ public interface IControlledDispatcherImpl : IDispatcherImplWithPendingInput
void RunLoop(CancellationToken token); void RunLoop(CancellationToken token);
} }
internal class LegacyDispatcherImpl : DefaultDispatcherClock, IControlledDispatcherImpl internal class LegacyDispatcherImpl : IControlledDispatcherImpl
{ {
private readonly IPlatformThreadingInterface _platformThreading; private readonly IPlatformThreadingInterface _platformThreading;
private IDisposable? _timer; private IDisposable? _timer;
private Stopwatch _clock = Stopwatch.StartNew();
public LegacyDispatcherImpl(IPlatformThreadingInterface platformThreading) public LegacyDispatcherImpl(IPlatformThreadingInterface platformThreading)
{ {
@ -56,14 +59,15 @@ internal class LegacyDispatcherImpl : DefaultDispatcherClock, IControlledDispatc
public event Action? Signaled; public event Action? Signaled;
public event Action? Timer; public event Action? Timer;
public void UpdateTimer(int? dueTimeInMs) public long Now => _clock.ElapsedMilliseconds;
public void UpdateTimer(long? dueTimeInMs)
{ {
_timer?.Dispose(); _timer?.Dispose();
_timer = null; _timer = null;
if (dueTimeInMs.HasValue) if (dueTimeInMs.HasValue)
{ {
var interval = Math.Max(1, dueTimeInMs.Value - TickCount); var interval = Math.Max(1, dueTimeInMs.Value - _clock.ElapsedMilliseconds);
_timer = _platformThreading.StartTimer(DispatcherPriority.Send, _timer = _platformThreading.StartTimer(DispatcherPriority.Send,
TimeSpan.FromMilliseconds(interval), TimeSpan.FromMilliseconds(interval),
OnTick); OnTick);
@ -94,7 +98,9 @@ class NullDispatcherImpl : IDispatcherImpl
public event Action? Signaled; public event Action? Signaled;
public event Action? Timer; public event Action? Timer;
public void UpdateTimer(int? dueTimeInMs) public long Now => 0;
public void UpdateTimer(long? dueTimeInMs)
{ {
} }

3
src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs

@ -40,7 +40,8 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
public event Action? Signaled; public event Action? Signaled;
public event Action? Timer; public event Action? Timer;
public void UpdateTimer(int? dueTimeInMs) public long Now => _clock.ElapsedMilliseconds;
public void UpdateTimer(long? dueTimeInMs)
{ {
lock (_lock) lock (_lock)
{ {

9
src/Avalonia.Native/DispatcherImpl.cs

@ -10,10 +10,11 @@ using MicroCom.Runtime;
namespace Avalonia.Native; namespace Avalonia.Native;
internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock, IDispatcherImplWithExplicitBackgroundProcessing internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherImplWithExplicitBackgroundProcessing
{ {
private readonly IAvnPlatformThreadingInterface _native; private readonly IAvnPlatformThreadingInterface _native;
private Thread? _loopThread; private Thread? _loopThread;
private Stopwatch _clock = Stopwatch.StartNew();
private Stack<RunLoopFrame> _managedFrames = new(); private Stack<RunLoopFrame> _managedFrames = new();
public DispatcherImpl(IAvnPlatformThreadingInterface native) public DispatcherImpl(IAvnPlatformThreadingInterface native)
@ -57,9 +58,9 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock, IDi
public void Signal() => _native.Signal(); public void Signal() => _native.Signal();
public void UpdateTimer(int? dueTimeInMs) public void UpdateTimer(long? dueTimeInMs)
{ {
var ms = dueTimeInMs == null ? -1 : Math.Max(1, dueTimeInMs.Value - TickCount); var ms = dueTimeInMs == null ? -1 : (int)Math.Min(int.MaxValue - 10, Math.Max(1, dueTimeInMs.Value - Now));
_native.UpdateTimer(ms); _native.UpdateTimer(ms);
} }
@ -113,7 +114,7 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock, IDi
} }
} }
public int TickCount => Environment.TickCount; public long Now => _clock.ElapsedMilliseconds;
public void PropagateCallbackException(ExceptionDispatchInfo capture) public void PropagateCallbackException(ExceptionDispatchInfo capture)
{ {

6
src/Avalonia.X11/X11PlatformThreading.cs

@ -9,7 +9,7 @@ using static Avalonia.X11.XLib;
namespace Avalonia.X11 namespace Avalonia.X11
{ {
internal unsafe class X11PlatformThreading : IControlledDispatcherImpl, IDispatcherClock internal unsafe class X11PlatformThreading : IControlledDispatcherImpl
{ {
private readonly AvaloniaX11Platform _platform; private readonly AvaloniaX11Platform _platform;
private readonly IntPtr _display; private readonly IntPtr _display;
@ -227,7 +227,7 @@ namespace Avalonia.X11
public event Action Signaled; public event Action Signaled;
public event Action Timer; public event Action Timer;
public void UpdateTimer(int? dueTimeInMs) public void UpdateTimer(long? dueTimeInMs)
{ {
_nextTimer = dueTimeInMs; _nextTimer = dueTimeInMs;
if (_nextTimer != null) if (_nextTimer != null)
@ -235,7 +235,7 @@ namespace Avalonia.X11
} }
public int TickCount => (int)_clock.ElapsedMilliseconds; public long Now => (int)_clock.ElapsedMilliseconds;
public bool CanQueryPendingInput => true; public bool CanQueryPendingInput => true;
public bool HasPendingInput => _platform.EventGrouperDispatchQueue.HasJobs || XPending(_display) != 0; public bool HasPendingInput => _platform.EventGrouperDispatchQueue.HasJobs || XPending(_display) != 0;

10
src/Windows/Avalonia.Win32/Win32DispatcherImpl.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using Avalonia.Threading; using Avalonia.Threading;
@ -6,10 +7,11 @@ using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32; namespace Avalonia.Win32;
internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock internal class Win32DispatcherImpl : IControlledDispatcherImpl
{ {
private readonly IntPtr _messageWindow; private readonly IntPtr _messageWindow;
private static Thread? s_uiThread; private static Thread? s_uiThread;
private readonly Stopwatch _clock = Stopwatch.StartNew();
public Win32DispatcherImpl(IntPtr messageWindow) public Win32DispatcherImpl(IntPtr messageWindow)
{ {
_messageWindow = messageWindow; _messageWindow = messageWindow;
@ -36,7 +38,7 @@ internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock
public void FireTimer() => Timer?.Invoke(); public void FireTimer() => Timer?.Invoke();
public void UpdateTimer(int? dueTimeInMs) public void UpdateTimer(long? dueTimeInMs)
{ {
if (dueTimeInMs == null) if (dueTimeInMs == null)
{ {
@ -44,7 +46,7 @@ internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock
} }
else else
{ {
var interval = (uint)Math.Max(1, TickCount - dueTimeInMs.Value); var interval = (uint)Math.Min(int.MaxValue - 10, Math.Max(1, Now - dueTimeInMs.Value));
SetTimer( SetTimer(
_messageWindow, _messageWindow,
(IntPtr)Win32Platform.TIMERID_DISPATCHER, (IntPtr)Win32Platform.TIMERID_DISPATCHER,
@ -115,5 +117,5 @@ internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock
} }
} }
public int TickCount => Environment.TickCount; public long Now => _clock.ElapsedMilliseconds;
} }

26
tests/Avalonia.Base.UnitTests/DispatcherTests.cs

@ -7,7 +7,7 @@ namespace Avalonia.Base.UnitTests;
public class DispatcherTests public class DispatcherTests
{ {
class SimpleDispatcherImpl : IDispatcherImpl, IDispatcherClock, IDispatcherImplWithPendingInput class SimpleDispatcherImpl : IDispatcherImpl, IDispatcherImplWithPendingInput
{ {
public bool CurrentThreadIsLoopThread => true; public bool CurrentThreadIsLoopThread => true;
@ -15,15 +15,15 @@ public class DispatcherTests
public event Action Signaled; public event Action Signaled;
public event Action Timer; public event Action Timer;
public int? NextTimer { get; private set; } public long? NextTimer { get; private set; }
public bool AskedForSignal { get; private set; } public bool AskedForSignal { get; private set; }
public void UpdateTimer(int? dueTimeInTicks) public void UpdateTimer(long? dueTimeInTicks)
{ {
NextTimer = dueTimeInTicks; NextTimer = dueTimeInTicks;
} }
public int TickCount { get; set; } public long Now { get; set; }
public void ExecuteSignal() public void ExecuteSignal()
{ {
@ -37,7 +37,7 @@ public class DispatcherTests
{ {
if (NextTimer == null) if (NextTimer == null)
return; return;
TickCount = NextTimer.Value; Now = NextTimer.Value;
Timer?.Invoke(); Timer?.Invoke();
} }
@ -51,7 +51,7 @@ public class DispatcherTests
public void DispatcherExecutesJobsAccordingToPriority() public void DispatcherExecutesJobsAccordingToPriority()
{ {
var impl = new SimpleDispatcherImpl(); var impl = new SimpleDispatcherImpl();
var disp = new Dispatcher(impl, impl); var disp = new Dispatcher(impl);
var actions = new List<string>(); var actions = new List<string>();
disp.Post(()=>actions.Add("Background"), DispatcherPriority.Background); disp.Post(()=>actions.Add("Background"), DispatcherPriority.Background);
disp.Post(()=>actions.Add("Render"), DispatcherPriority.Render); disp.Post(()=>actions.Add("Render"), DispatcherPriority.Render);
@ -65,7 +65,7 @@ public class DispatcherTests
public void DispatcherPreservesOrderWhenChangingPriority() public void DispatcherPreservesOrderWhenChangingPriority()
{ {
var impl = new SimpleDispatcherImpl(); var impl = new SimpleDispatcherImpl();
var disp = new Dispatcher(impl, impl); var disp = new Dispatcher(impl);
var actions = new List<string>(); var actions = new List<string>();
var toPromote = disp.InvokeAsync(()=>actions.Add("PromotedRender"), DispatcherPriority.Background); var toPromote = disp.InvokeAsync(()=>actions.Add("PromotedRender"), DispatcherPriority.Background);
var toPromote2 = disp.InvokeAsync(()=>actions.Add("PromotedRender2"), DispatcherPriority.Input); var toPromote2 = disp.InvokeAsync(()=>actions.Add("PromotedRender2"), DispatcherPriority.Input);
@ -84,7 +84,7 @@ public class DispatcherTests
public void DispatcherStopsItemProcessingWhenInteractivityDeadlineIsReached() public void DispatcherStopsItemProcessingWhenInteractivityDeadlineIsReached()
{ {
var impl = new SimpleDispatcherImpl(); var impl = new SimpleDispatcherImpl();
var disp = new Dispatcher(impl, impl); var disp = new Dispatcher(impl);
var actions = new List<int>(); var actions = new List<int>();
for (var c = 0; c < 10; c++) for (var c = 0; c < 10; c++)
{ {
@ -92,7 +92,7 @@ public class DispatcherTests
disp.Post(() => disp.Post(() =>
{ {
actions.Add(itemId); actions.Add(itemId);
impl.TickCount += 20; impl.Now += 20;
}, DispatcherPriority.Background); }, DispatcherPriority.Background);
} }
@ -114,7 +114,7 @@ public class DispatcherTests
Assert.False(impl.AskedForSignal); Assert.False(impl.AskedForSignal);
if (c < 3) if (c < 3)
{ {
Assert.True(impl.NextTimer > impl.TickCount); Assert.True(impl.NextTimer > impl.Now);
} }
else else
Assert.Null(impl.NextTimer); Assert.Null(impl.NextTimer);
@ -127,7 +127,7 @@ public class DispatcherTests
{ {
var impl = new SimpleDispatcherImpl(); var impl = new SimpleDispatcherImpl();
impl.TestInputPending = true; impl.TestInputPending = true;
var disp = new Dispatcher(impl, impl); var disp = new Dispatcher(impl);
var actions = new List<int>(); var actions = new List<int>();
for (var c = 0; c < 10; c++) for (var c = 0; c < 10; c++)
{ {
@ -160,8 +160,8 @@ public class DispatcherTests
Assert.False(impl.AskedForSignal); Assert.False(impl.AskedForSignal);
if (c < 3) if (c < 3)
{ {
Assert.True(impl.NextTimer > impl.TickCount); Assert.True(impl.NextTimer > impl.Now);
impl.TickCount = impl.NextTimer.Value + 1; impl.Now = impl.NextTimer.Value + 1;
} }
else else
Assert.Null(impl.NextTimer); Assert.Null(impl.NextTimer);

Loading…
Cancel
Save