From dbbdcb95cd0ceb76f777620fa12473e764890726 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 19 Mar 2023 22:19:55 +0600 Subject: [PATCH] Refactored dispatcher clock --- .../Threading/Dispatcher.Queue.cs | 11 ++++---- .../Threading/Dispatcher.Timers.cs | 18 +++++++------ src/Avalonia.Base/Threading/Dispatcher.cs | 6 ++--- .../Threading/DispatcherTimer.cs | 6 ++--- .../Threading/IDispatcherClock.cs | 13 ---------- .../Threading/IDispatcherImpl.cs | 16 ++++++++---- .../Platform/ManagedDispatcherImpl.cs | 3 ++- src/Avalonia.Native/DispatcherImpl.cs | 9 ++++--- src/Avalonia.X11/X11PlatformThreading.cs | 6 ++--- .../Avalonia.Win32/Win32DispatcherImpl.cs | 10 ++++--- .../DispatcherTests.cs | 26 +++++++++---------- 11 files changed, 61 insertions(+), 63 deletions(-) delete mode 100644 src/Avalonia.Base/Threading/IDispatcherClock.cs diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs index 105019f277..c91af1a514 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs @@ -23,7 +23,7 @@ public partial class Dispatcher } else if (_dueTimeForBackgroundProcessing == null) { - _dueTimeForBackgroundProcessing = Clock.TickCount + 1; + _dueTimeForBackgroundProcessing = Now + 1; UpdateOSTimer(); } } @@ -68,7 +68,8 @@ public partial class Dispatcher public event Action? Signaled; 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() { - int? backgroundJobExecutionStartedAt = null; + long? backgroundJobExecutionStartedAt = null; while (true) { DispatcherOperation? job; @@ -153,9 +154,9 @@ public partial class Dispatcher else { if (backgroundJobExecutionStartedAt == null) - backgroundJobExecutionStartedAt = Clock.TickCount; + backgroundJobExecutionStartedAt = Now; - if (Clock.TickCount - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs) + if (Now - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs) { _signaled = true; RequestBackgroundProcessing(); diff --git a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs index 269d10707e..bb252b7f55 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs @@ -9,11 +9,13 @@ public partial class Dispatcher private List _timers = new(); private long _timersVersion; private bool _dueTimeFound; - private int _dueTimeInMs; + private long _dueTimeInMs; - private int? _dueTimeForTimers; - private int? _dueTimeForBackgroundProcessing; - private int? _osTimerSetTo; + private long? _dueTimeForTimers; + private long? _dueTimeForBackgroundProcessing; + private long? _osTimerSetTo; + + internal long Now => _impl.Now; private void UpdateOSTimer() { @@ -40,7 +42,7 @@ public partial class Dispatcher if (!_hasShutdownFinished) // Dispatcher thread, does not technically need the lock to read { bool oldDueTimeFound = _dueTimeFound; - int oldDueTimeInTicks = _dueTimeInMs; + long oldDueTimeInTicks = _dueTimeInMs; _dueTimeFound = false; _dueTimeInMs = 0; @@ -113,11 +115,11 @@ public partial class Dispatcher lock (InstanceLock) { _impl.UpdateTimer(_osTimerSetTo = null); - needToPromoteTimers = _dueTimeForTimers.HasValue && _dueTimeForTimers.Value <= Clock.TickCount; + needToPromoteTimers = _dueTimeForTimers.HasValue && _dueTimeForTimers.Value <= Now; if (needToPromoteTimers) _dueTimeForTimers = null; needToProcessQueue = _dueTimeForBackgroundProcessing.HasValue && - _dueTimeForBackgroundProcessing.Value <= Clock.TickCount; + _dueTimeForBackgroundProcessing.Value <= Now; if (needToProcessQueue) _dueTimeForBackgroundProcessing = null; } @@ -131,7 +133,7 @@ public partial class Dispatcher internal void PromoteTimers() { - int currentTimeInTicks = Clock.TickCount; + long currentTimeInTicks = Now; try { List? timers = null; diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index d1bd15e286..25a4a4ce2c 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -17,7 +17,6 @@ namespace Avalonia.Threading; public partial class Dispatcher : IDispatcher { private IDispatcherImpl _impl; - internal IDispatcherClock Clock { get; } internal object InstanceLock { get; } = new(); private bool _hasShutdownFinished; private IControlledDispatcherImpl? _controlledImpl; @@ -25,10 +24,9 @@ public partial class Dispatcher : IDispatcher private IDispatcherImplWithPendingInput? _pendingInputImpl; private IDispatcherImplWithExplicitBackgroundProcessing? _backgroundProcessingImpl; - internal Dispatcher(IDispatcherImpl impl, IDispatcherClock clock) + internal Dispatcher(IDispatcherImpl impl) { _impl = impl; - Clock = clock; impl.Timer += OnOSTimer; impl.Signaled += Signaled; _controlledImpl = _impl as IControlledDispatcherImpl; @@ -51,7 +49,7 @@ public partial class Dispatcher : IDispatcher else impl = new NullDispatcherImpl(); } - return new Dispatcher(impl, impl as IDispatcherClock ?? new DefaultDispatcherClock()); + return new Dispatcher(impl); } /// diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 0c235ee161..879d9d8a5f 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -125,7 +125,7 @@ public partial class DispatcherTimer if (_isEnabled) { - DueTimeInMs = _dispatcher.Clock.TickCount + (int)_interval.TotalMilliseconds; + DueTimeInMs = _dispatcher.Now + (long)_interval.TotalMilliseconds; updateOSTimer = true; } } @@ -288,7 +288,7 @@ public partial class DispatcherTimer // BeginInvoke a new operation. _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()) { @@ -348,5 +348,5 @@ public partial class DispatcherTimer private bool _isEnabled; // used by Dispatcher - internal int DueTimeInMs { get; private set; } + internal long DueTimeInMs { get; private set; } } \ No newline at end of file diff --git a/src/Avalonia.Base/Threading/IDispatcherClock.cs b/src/Avalonia.Base/Threading/IDispatcherClock.cs deleted file mode 100644 index 2a5268d192..0000000000 --- a/src/Avalonia.Base/Threading/IDispatcherClock.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Avalonia.Threading; - -internal interface IDispatcherClock -{ - int TickCount { get; } -} - -internal class DefaultDispatcherClock : IDispatcherClock -{ - public int TickCount => Environment.TickCount; -} \ No newline at end of file diff --git a/src/Avalonia.Base/Threading/IDispatcherImpl.cs b/src/Avalonia.Base/Threading/IDispatcherImpl.cs index 2cc06d1986..670ec55461 100644 --- a/src/Avalonia.Base/Threading/IDispatcherImpl.cs +++ b/src/Avalonia.Base/Threading/IDispatcherImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Threading; using Avalonia.Metadata; using Avalonia.Platform; @@ -14,7 +15,8 @@ public interface IDispatcherImpl void Signal(); event Action Signaled; event Action Timer; - void UpdateTimer(int? dueTimeInMs); + long Now { get; } + void UpdateTimer(long? dueTimeInMs); } [Unstable] @@ -40,10 +42,11 @@ public interface IControlledDispatcherImpl : IDispatcherImplWithPendingInput void RunLoop(CancellationToken token); } -internal class LegacyDispatcherImpl : DefaultDispatcherClock, IControlledDispatcherImpl +internal class LegacyDispatcherImpl : IControlledDispatcherImpl { private readonly IPlatformThreadingInterface _platformThreading; private IDisposable? _timer; + private Stopwatch _clock = Stopwatch.StartNew(); public LegacyDispatcherImpl(IPlatformThreadingInterface platformThreading) { @@ -56,14 +59,15 @@ internal class LegacyDispatcherImpl : DefaultDispatcherClock, IControlledDispatc public event Action? Signaled; public event Action? Timer; - public void UpdateTimer(int? dueTimeInMs) + public long Now => _clock.ElapsedMilliseconds; + public void UpdateTimer(long? dueTimeInMs) { _timer?.Dispose(); _timer = null; 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, TimeSpan.FromMilliseconds(interval), OnTick); @@ -94,7 +98,9 @@ class NullDispatcherImpl : IDispatcherImpl public event Action? Signaled; public event Action? Timer; - public void UpdateTimer(int? dueTimeInMs) + public long Now => 0; + + public void UpdateTimer(long? dueTimeInMs) { } diff --git a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs b/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs index 54c96113ea..20aa91c83e 100644 --- a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs +++ b/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs @@ -40,7 +40,8 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl public event Action? Signaled; public event Action? Timer; - public void UpdateTimer(int? dueTimeInMs) + public long Now => _clock.ElapsedMilliseconds; + public void UpdateTimer(long? dueTimeInMs) { lock (_lock) { diff --git a/src/Avalonia.Native/DispatcherImpl.cs b/src/Avalonia.Native/DispatcherImpl.cs index b1d3cb59de..fd8ef567f4 100644 --- a/src/Avalonia.Native/DispatcherImpl.cs +++ b/src/Avalonia.Native/DispatcherImpl.cs @@ -10,10 +10,11 @@ using MicroCom.Runtime; namespace Avalonia.Native; -internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock, IDispatcherImplWithExplicitBackgroundProcessing +internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherImplWithExplicitBackgroundProcessing { private readonly IAvnPlatformThreadingInterface _native; private Thread? _loopThread; + private Stopwatch _clock = Stopwatch.StartNew(); private Stack _managedFrames = new(); public DispatcherImpl(IAvnPlatformThreadingInterface native) @@ -57,9 +58,9 @@ internal class DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock, IDi 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); } @@ -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) { diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs index f2f45bce8e..de0e3bee5d 100644 --- a/src/Avalonia.X11/X11PlatformThreading.cs +++ b/src/Avalonia.X11/X11PlatformThreading.cs @@ -9,7 +9,7 @@ using static Avalonia.X11.XLib; namespace Avalonia.X11 { - internal unsafe class X11PlatformThreading : IControlledDispatcherImpl, IDispatcherClock + internal unsafe class X11PlatformThreading : IControlledDispatcherImpl { private readonly AvaloniaX11Platform _platform; private readonly IntPtr _display; @@ -227,7 +227,7 @@ namespace Avalonia.X11 public event Action Signaled; public event Action Timer; - public void UpdateTimer(int? dueTimeInMs) + public void UpdateTimer(long? dueTimeInMs) { _nextTimer = dueTimeInMs; 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 HasPendingInput => _platform.EventGrouperDispatchQueue.HasJobs || XPending(_display) != 0; diff --git a/src/Windows/Avalonia.Win32/Win32DispatcherImpl.cs b/src/Windows/Avalonia.Win32/Win32DispatcherImpl.cs index 3c2f7842ba..581e5fa306 100644 --- a/src/Windows/Avalonia.Win32/Win32DispatcherImpl.cs +++ b/src/Windows/Avalonia.Win32/Win32DispatcherImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Threading; @@ -6,10 +7,11 @@ using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32; -internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock +internal class Win32DispatcherImpl : IControlledDispatcherImpl { private readonly IntPtr _messageWindow; private static Thread? s_uiThread; + private readonly Stopwatch _clock = Stopwatch.StartNew(); public Win32DispatcherImpl(IntPtr messageWindow) { _messageWindow = messageWindow; @@ -36,7 +38,7 @@ internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock public void FireTimer() => Timer?.Invoke(); - public void UpdateTimer(int? dueTimeInMs) + public void UpdateTimer(long? dueTimeInMs) { if (dueTimeInMs == null) { @@ -44,7 +46,7 @@ internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock } 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( _messageWindow, (IntPtr)Win32Platform.TIMERID_DISPATCHER, @@ -115,5 +117,5 @@ internal class Win32DispatcherImpl : IControlledDispatcherImpl, IDispatcherClock } } - public int TickCount => Environment.TickCount; + public long Now => _clock.ElapsedMilliseconds; } diff --git a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs index 902af94121..38175ad410 100644 --- a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs +++ b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs @@ -7,7 +7,7 @@ namespace Avalonia.Base.UnitTests; public class DispatcherTests { - class SimpleDispatcherImpl : IDispatcherImpl, IDispatcherClock, IDispatcherImplWithPendingInput + class SimpleDispatcherImpl : IDispatcherImpl, IDispatcherImplWithPendingInput { public bool CurrentThreadIsLoopThread => true; @@ -15,15 +15,15 @@ public class DispatcherTests public event Action Signaled; public event Action Timer; - public int? NextTimer { get; private set; } + public long? NextTimer { get; private set; } public bool AskedForSignal { get; private set; } - public void UpdateTimer(int? dueTimeInTicks) + public void UpdateTimer(long? dueTimeInTicks) { NextTimer = dueTimeInTicks; } - public int TickCount { get; set; } + public long Now { get; set; } public void ExecuteSignal() { @@ -37,7 +37,7 @@ public class DispatcherTests { if (NextTimer == null) return; - TickCount = NextTimer.Value; + Now = NextTimer.Value; Timer?.Invoke(); } @@ -51,7 +51,7 @@ public class DispatcherTests public void DispatcherExecutesJobsAccordingToPriority() { var impl = new SimpleDispatcherImpl(); - var disp = new Dispatcher(impl, impl); + var disp = new Dispatcher(impl); var actions = new List(); disp.Post(()=>actions.Add("Background"), DispatcherPriority.Background); disp.Post(()=>actions.Add("Render"), DispatcherPriority.Render); @@ -65,7 +65,7 @@ public class DispatcherTests public void DispatcherPreservesOrderWhenChangingPriority() { var impl = new SimpleDispatcherImpl(); - var disp = new Dispatcher(impl, impl); + var disp = new Dispatcher(impl); var actions = new List(); var toPromote = disp.InvokeAsync(()=>actions.Add("PromotedRender"), DispatcherPriority.Background); var toPromote2 = disp.InvokeAsync(()=>actions.Add("PromotedRender2"), DispatcherPriority.Input); @@ -84,7 +84,7 @@ public class DispatcherTests public void DispatcherStopsItemProcessingWhenInteractivityDeadlineIsReached() { var impl = new SimpleDispatcherImpl(); - var disp = new Dispatcher(impl, impl); + var disp = new Dispatcher(impl); var actions = new List(); for (var c = 0; c < 10; c++) { @@ -92,7 +92,7 @@ public class DispatcherTests disp.Post(() => { actions.Add(itemId); - impl.TickCount += 20; + impl.Now += 20; }, DispatcherPriority.Background); } @@ -114,7 +114,7 @@ public class DispatcherTests Assert.False(impl.AskedForSignal); if (c < 3) { - Assert.True(impl.NextTimer > impl.TickCount); + Assert.True(impl.NextTimer > impl.Now); } else Assert.Null(impl.NextTimer); @@ -127,7 +127,7 @@ public class DispatcherTests { var impl = new SimpleDispatcherImpl(); impl.TestInputPending = true; - var disp = new Dispatcher(impl, impl); + var disp = new Dispatcher(impl); var actions = new List(); for (var c = 0; c < 10; c++) { @@ -160,8 +160,8 @@ public class DispatcherTests Assert.False(impl.AskedForSignal); if (c < 3) { - Assert.True(impl.NextTimer > impl.TickCount); - impl.TickCount = impl.NextTimer.Value + 1; + Assert.True(impl.NextTimer > impl.Now); + impl.Now = impl.NextTimer.Value + 1; } else Assert.Null(impl.NextTimer);