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 readonly DispatcherPriorityQueue _queue = new();
private bool _signaled; private bool _signaled;
private DispatcherTimer? _backgroundTimer;
private const int MaximumTimeProcessingBackgroundJobs = 50; private const int MaximumTimeProcessingBackgroundJobs = 50;
void RequestBackgroundProcessing() void RequestBackgroundProcessing()
{ {
if (_backgroundTimer == null) lock (InstanceLock)
{ {
_backgroundTimer = if (_dueTimeForBackgroundProcessing == null)
new DispatcherTimer(this, DispatcherPriority.Send,
TimeSpan.FromMilliseconds(1))
{
Tag = "Dispatcher.RequestBackgroundProcessing"
};
_backgroundTimer.Tick += delegate
{ {
_backgroundTimer.Stop(); _dueTimeForBackgroundProcessing = Clock.TickCount + 1;
}; UpdateOSTimer();
}
} }
_backgroundTimer.IsEnabled = true;
} }
/// <summary> /// <summary>
@ -167,8 +159,19 @@ public partial class Dispatcher
{ {
lock (InstanceLock) lock (InstanceLock)
{ {
if (!CheckAccess())
{
RequestForegroundProcessing();
return true;
}
if (_queue.MaxPriority <= DispatcherPriority.Input) if (_queue.MaxPriority <= DispatcherPriority.Input)
RequestBackgroundProcessing(); {
if (_pendingInputImpl is { CanQueryPendingInput: true, HasPendingInput: false })
RequestForegroundProcessing();
else
RequestBackgroundProcessing();
}
else else
RequestForegroundProcessing(); RequestForegroundProcessing();
} }

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

@ -10,13 +10,30 @@ public partial class Dispatcher
private long _timersVersion; private long _timersVersion;
private bool _dueTimeFound; private bool _dueTimeFound;
private int _dueTimeInMs; 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()) if (!CheckAccess())
{ {
Post(UpdateOSTimer, DispatcherPriority.Send); Post(UpdateOSTimerForTimers, DispatcherPriority.Send);
return; return;
} }
@ -46,16 +63,16 @@ public partial class Dispatcher
if (_dueTimeFound) if (_dueTimeFound)
{ {
if (!_isOsTimerSet || !oldDueTimeFound || (oldDueTimeInTicks != _dueTimeInMs)) if (_dueTimeForTimers == null || !oldDueTimeFound || (oldDueTimeInTicks != _dueTimeInMs))
{ {
_impl.UpdateTimer(Math.Max(1, _dueTimeInMs)); _dueTimeForTimers = _dueTimeInMs;
_isOsTimerSet = true; UpdateOSTimer();
} }
} }
else if (oldDueTimeFound) else if (oldDueTimeFound)
{ {
_impl.UpdateTimer(null); _dueTimeForTimers = null;
_isOsTimerSet = false; UpdateOSTimer();
} }
} }
} }
@ -72,7 +89,7 @@ public partial class Dispatcher
} }
} }
UpdateOSTimer(); UpdateOSTimerForTimers();
} }
internal void RemoveTimer(DispatcherTimer timer) internal void RemoveTimer(DispatcherTimer timer)
@ -86,17 +103,29 @@ public partial class Dispatcher
} }
} }
UpdateOSTimer(); UpdateOSTimerForTimers();
} }
private void OnOSTimer() private void OnOSTimer()
{ {
bool needToPromoteTimers = false;
bool needToProcessQueue = false;
lock (InstanceLock) lock (InstanceLock)
{ {
_impl.UpdateTimer(null); needToPromoteTimers = _dueTimeForTimers.HasValue && _dueTimeForTimers.Value <= Clock.TickCount;
_isOsTimerSet = false; 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() internal void PromoteTimers()
@ -166,10 +195,10 @@ public partial class Dispatcher
} }
finally finally
{ {
UpdateOSTimer(); UpdateOSTimerForTimers();
} }
} }
internal static List<DispatcherTimer> SnapshotTimersForUnitTests() => 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) 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.False(impl.AskedForSignal);
Assert.NotNull(impl.NextTimer); Assert.NotNull(impl.NextTimer);
impl.ExecuteTimer();
Assert.True(impl.AskedForSignal);
Assert.Null(impl.NextTimer);
for (var c = 0; c < 4; c++) for (var c = 0; c < 4; c++)
{ {
if (impl.NextTimer != null) Assert.NotNull(impl.NextTimer);
impl.ExecuteTimer(); Assert.False(impl.AskedForSignal);
Assert.True(impl.AskedForSignal); impl.ExecuteTimer();
Assert.False(impl.AskedForSignal);
impl.ExecuteSignal(); impl.ExecuteSignal();
var expectedCount = (c + 1) * 3; var expectedCount = (c + 1) * 3;
if (c == 3) if (c == 3)
@ -129,7 +126,7 @@ public class DispatcherTests
public void DispatcherStopsItemProcessingWhenInputIsPending() public void DispatcherStopsItemProcessingWhenInputIsPending()
{ {
var impl = new SimpleDispatcherImpl(); var impl = new SimpleDispatcherImpl();
impl.TestInputPending = false; impl.TestInputPending = true;
var disp = new Dispatcher(impl, impl); var disp = new Dispatcher(impl, 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++)
@ -144,17 +141,13 @@ public class DispatcherTests
} }
Assert.False(impl.AskedForSignal); Assert.False(impl.AskedForSignal);
Assert.NotNull(impl.NextTimer); Assert.NotNull(impl.NextTimer);
impl.TestInputPending = false;
impl.ExecuteTimer();
Assert.True(impl.AskedForSignal);
Assert.Null(impl.NextTimer);
for (var c = 0; c < 4; c++) for (var c = 0; c < 4; c++)
{ {
if (impl.NextTimer != null) Assert.NotNull(impl.NextTimer);
impl.ExecuteTimer(); impl.ExecuteTimer();
Assert.True(impl.AskedForSignal); Assert.False(impl.AskedForSignal);
impl.ExecuteSignal();
var expectedCount = c switch var expectedCount = c switch
{ {
0 => 1, 0 => 1,

Loading…
Cancel
Save