From d96b8124ed63c72a9ca176fb17d4a1dc8b09e808 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 18 Mar 2023 17:03:04 +0600 Subject: [PATCH] Don't use DispatcherTimer infrastructure for background processing --- .../Threading/Dispatcher.Queue.cs | 31 +++++----- .../Threading/Dispatcher.Timers.cs | 59 ++++++++++++++----- .../Threading/DispatcherTimer.cs | 2 +- .../DispatcherTests.cs | 25 +++----- 4 files changed, 71 insertions(+), 46 deletions(-) diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs index 1550da079f..4360d8111d 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs @@ -7,26 +7,18 @@ public partial class Dispatcher { private readonly DispatcherPriorityQueue _queue = new(); private bool _signaled; - private DispatcherTimer? _backgroundTimer; private const int MaximumTimeProcessingBackgroundJobs = 50; void RequestBackgroundProcessing() { - if (_backgroundTimer == null) + lock (InstanceLock) { - _backgroundTimer = - new DispatcherTimer(this, DispatcherPriority.Send, - TimeSpan.FromMilliseconds(1)) - { - Tag = "Dispatcher.RequestBackgroundProcessing" - }; - _backgroundTimer.Tick += delegate + if (_dueTimeForBackgroundProcessing == null) { - _backgroundTimer.Stop(); - }; + _dueTimeForBackgroundProcessing = Clock.TickCount + 1; + UpdateOSTimer(); + } } - - _backgroundTimer.IsEnabled = true; } /// @@ -167,8 +159,19 @@ public partial class Dispatcher { lock (InstanceLock) { + if (!CheckAccess()) + { + RequestForegroundProcessing(); + return true; + } + if (_queue.MaxPriority <= DispatcherPriority.Input) - RequestBackgroundProcessing(); + { + if (_pendingInputImpl is { CanQueryPendingInput: true, HasPendingInput: false }) + RequestForegroundProcessing(); + else + RequestBackgroundProcessing(); + } else RequestForegroundProcessing(); } diff --git a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs index 0f9924d90d..0bf087918e 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs @@ -10,13 +10,30 @@ public partial class Dispatcher private long _timersVersion; private bool _dueTimeFound; private int _dueTimeInMs; - private bool _isOsTimerSet; - internal void UpdateOSTimer() + private int? _dueTimeForTimers; + private int? _dueTimeForBackgroundProcessing; + private int? _osTimerSetTo; + + private void UpdateOSTimer() + { + lock (InstanceLock) + { + var nextDueTime = + (_dueTimeForTimers.HasValue && _dueTimeForBackgroundProcessing.HasValue) + ? Math.Min(_dueTimeForTimers.Value, _dueTimeForBackgroundProcessing.Value) + : _dueTimeForTimers ?? _dueTimeForBackgroundProcessing; + if(_osTimerSetTo == nextDueTime) + return; + _impl.UpdateTimer(_osTimerSetTo = nextDueTime); + } + } + + internal void UpdateOSTimerForTimers() { if (!CheckAccess()) { - Post(UpdateOSTimer, DispatcherPriority.Send); + Post(UpdateOSTimerForTimers, DispatcherPriority.Send); return; } @@ -46,16 +63,16 @@ public partial class Dispatcher if (_dueTimeFound) { - if (!_isOsTimerSet || !oldDueTimeFound || (oldDueTimeInTicks != _dueTimeInMs)) + if (_dueTimeForTimers == null || !oldDueTimeFound || (oldDueTimeInTicks != _dueTimeInMs)) { - _impl.UpdateTimer(Math.Max(1, _dueTimeInMs)); - _isOsTimerSet = true; + _dueTimeForTimers = _dueTimeInMs; + UpdateOSTimer(); } } else if (oldDueTimeFound) { - _impl.UpdateTimer(null); - _isOsTimerSet = false; + _dueTimeForTimers = null; + UpdateOSTimer(); } } } @@ -72,7 +89,7 @@ public partial class Dispatcher } } - UpdateOSTimer(); + UpdateOSTimerForTimers(); } internal void RemoveTimer(DispatcherTimer timer) @@ -86,17 +103,29 @@ public partial class Dispatcher } } - UpdateOSTimer(); + UpdateOSTimerForTimers(); } private void OnOSTimer() { + bool needToPromoteTimers = false; + bool needToProcessQueue = false; lock (InstanceLock) { - _impl.UpdateTimer(null); - _isOsTimerSet = false; + needToPromoteTimers = _dueTimeForTimers.HasValue && _dueTimeForTimers.Value <= Clock.TickCount; + if (needToPromoteTimers) + _dueTimeForTimers = null; + needToProcessQueue = _dueTimeForBackgroundProcessing.HasValue && + _dueTimeForBackgroundProcessing.Value <= Clock.TickCount; + if (needToProcessQueue) + _dueTimeForBackgroundProcessing = null; + UpdateOSTimer(); } - PromoteTimers(); + + if (needToPromoteTimers) + PromoteTimers(); + if (needToProcessQueue) + ExecuteJobsCore(); } internal void PromoteTimers() @@ -166,10 +195,10 @@ public partial class Dispatcher } finally { - UpdateOSTimer(); + UpdateOSTimerForTimers(); } } internal static List SnapshotTimersForUnitTests() => - s_uiThread!._timers.Where(t => t != s_uiThread._backgroundTimer).ToList(); + s_uiThread!._timers.ToList(); } \ No newline at end of file diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 183f66eb61..f7ed03379e 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -132,7 +132,7 @@ public partial class DispatcherTimer if (updateOSTimer) { - _dispatcher.UpdateOSTimer(); + _dispatcher.UpdateOSTimerForTimers(); } } } diff --git a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs index 450c857cfe..902af94121 100644 --- a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs +++ b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs @@ -99,15 +99,12 @@ public class DispatcherTests Assert.False(impl.AskedForSignal); Assert.NotNull(impl.NextTimer); - impl.ExecuteTimer(); - Assert.True(impl.AskedForSignal); - Assert.Null(impl.NextTimer); - for (var c = 0; c < 4; c++) { - if (impl.NextTimer != null) - impl.ExecuteTimer(); - Assert.True(impl.AskedForSignal); + Assert.NotNull(impl.NextTimer); + Assert.False(impl.AskedForSignal); + impl.ExecuteTimer(); + Assert.False(impl.AskedForSignal); impl.ExecuteSignal(); var expectedCount = (c + 1) * 3; if (c == 3) @@ -129,7 +126,7 @@ public class DispatcherTests public void DispatcherStopsItemProcessingWhenInputIsPending() { var impl = new SimpleDispatcherImpl(); - impl.TestInputPending = false; + impl.TestInputPending = true; var disp = new Dispatcher(impl, impl); var actions = new List(); for (var c = 0; c < 10; c++) @@ -144,17 +141,13 @@ public class DispatcherTests } Assert.False(impl.AskedForSignal); Assert.NotNull(impl.NextTimer); - - impl.ExecuteTimer(); - Assert.True(impl.AskedForSignal); - Assert.Null(impl.NextTimer); + impl.TestInputPending = false; for (var c = 0; c < 4; c++) { - if (impl.NextTimer != null) - impl.ExecuteTimer(); - Assert.True(impl.AskedForSignal); - impl.ExecuteSignal(); + Assert.NotNull(impl.NextTimer); + impl.ExecuteTimer(); + Assert.False(impl.AskedForSignal); var expectedCount = c switch { 0 => 1,