Browse Source

Don't use DispatcherTimer infrastructure for background processing

Nikita Tsukanov 2 years ago
parent
commit
d96b8124ed

+ 17 - 14
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;
     }
 
     /// <summary>
@@ -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();
         }

+ 44 - 15
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<DispatcherTimer> SnapshotTimersForUnitTests() =>
-        s_uiThread!._timers.Where(t => t != s_uiThread._backgroundTimer).ToList();
+        s_uiThread!._timers.ToList();
 }

+ 1 - 1
src/Avalonia.Base/Threading/DispatcherTimer.cs

@@ -132,7 +132,7 @@ public partial class DispatcherTimer
 
             if (updateOSTimer)
             {
-                _dispatcher.UpdateOSTimer();
+                _dispatcher.UpdateOSTimerForTimers();
             }
         }
     }

+ 9 - 16
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<int>();
         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,