Browse Source

Refactored dispatcher clock

pull/10691/head
Nikita Tsukanov 3 years ago
parent
commit
dbbdcb95cd
  1. 11
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  2. 18
      src/Avalonia.Base/Threading/Dispatcher.Timers.cs
  3. 6
      src/Avalonia.Base/Threading/Dispatcher.cs
  4. 6
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  5. 13
      src/Avalonia.Base/Threading/IDispatcherClock.cs
  6. 16
      src/Avalonia.Base/Threading/IDispatcherImpl.cs
  7. 3
      src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
  8. 9
      src/Avalonia.Native/DispatcherImpl.cs
  9. 6
      src/Avalonia.X11/X11PlatformThreading.cs
  10. 10
      src/Windows/Avalonia.Win32/Win32DispatcherImpl.cs
  11. 26
      tests/Avalonia.Base.UnitTests/DispatcherTests.cs

11
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();

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

@ -9,11 +9,13 @@ public partial class Dispatcher
private List<DispatcherTimer> _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<DispatcherTimer>? timers = null;

6
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);
}
/// <summary>

6
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; }
}

13
src/Avalonia.Base/Threading/IDispatcherClock.cs

@ -1,13 +0,0 @@
using System;
namespace Avalonia.Threading;
internal interface IDispatcherClock
{
int TickCount { get; }
}
internal class DefaultDispatcherClock : IDispatcherClock
{
public int TickCount => Environment.TickCount;
}

16
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)
{
}

3
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)
{

9
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<RunLoopFrame> _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)
{

6
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;

10
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;
}

26
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<string>();
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<string>();
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<int>();
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<int>();
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);

Loading…
Cancel
Save