Browse Source

Don't use DispatcherTimer infrastructure for background processing

pull/10691/head
Nikita Tsukanov 4 years ago
parent
commit
d96b8124ed
  1. 31
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  2. 59
      src/Avalonia.Base/Threading/Dispatcher.Timers.cs
  3. 2
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  4. 25
      tests/Avalonia.Base.UnitTests/DispatcherTests.cs

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

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

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

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

25
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,

Loading…
Cancel
Save