123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Threading;
- namespace Avalonia.Threading;
- public partial class Dispatcher
- {
- private readonly DispatcherPriorityQueue _queue = new();
- private bool _signaled;
- private bool _explicitBackgroundProcessingRequested;
- private const int MaximumInputStarvationTimeInFallbackMode = 50;
- private const int MaximumInputStarvationTimeInExplicitProcessingExplicitMode = 50;
- private readonly int _maximumInputStarvationTime;
-
- void RequestBackgroundProcessing()
- {
- lock (InstanceLock)
- {
- if (_backgroundProcessingImpl != null)
- {
- if(_explicitBackgroundProcessingRequested)
- return;
- _explicitBackgroundProcessingRequested = true;
- _backgroundProcessingImpl.RequestBackgroundProcessing();
- }
- else if (_dueTimeForBackgroundProcessing == null)
- {
- _dueTimeForBackgroundProcessing = Now + 1;
- UpdateOSTimer();
- }
- }
- }
- private void OnReadyForExplicitBackgroundProcessing()
- {
- lock (InstanceLock)
- {
- _explicitBackgroundProcessingRequested = false;
- }
- ExecuteJobsCore(true);
- }
- /// <summary>
- /// Force-runs all dispatcher operations ignoring any pending OS events, use with caution
- /// </summary>
- public void RunJobs(DispatcherPriority? priority = null)
- {
- RunJobs(priority, CancellationToken.None);
- }
-
- internal void RunJobs(DispatcherPriority? priority, CancellationToken cancellationToken)
- {
- if (DisabledProcessingCount > 0)
- throw new InvalidOperationException(
- "Cannot perform this operation while dispatcher processing is suspended.");
-
- priority ??= DispatcherPriority.MinimumActiveValue;
- if (priority < DispatcherPriority.MinimumActiveValue)
- priority = DispatcherPriority.MinimumActiveValue;
- while (!cancellationToken.IsCancellationRequested)
- {
- DispatcherOperation? job;
- lock (InstanceLock)
- job = _queue.Peek();
- if (job == null)
- return;
- if (job.Priority < priority.Value)
- return;
- ExecuteJob(job);
- }
- }
- private sealed class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
- {
- public bool CurrentThreadIsLoopThread => true;
- public void Signal()
- {
- }
- public event Action? Signaled
- {
- add { }
- remove { }
- }
- public event Action? Timer
- {
- add { }
- remove { }
- }
- public long Now => 0;
- public void UpdateTimer(long? dueTimeInMs)
- {
- }
- }
- internal static void ResetBeforeUnitTests()
- {
- s_uiThread = null;
- }
-
- internal static void ResetForUnitTests()
- {
- if (s_uiThread == null)
- return;
- var st = Stopwatch.StartNew();
- while (true)
- {
- s_uiThread._pendingInputImpl = s_uiThread._controlledImpl = null;
- s_uiThread._impl = new DummyShuttingDownUnitTestDispatcherImpl();
- if (st.Elapsed.TotalSeconds > 5)
- throw new InvalidProgramException("You've caused dispatcher loop");
-
- DispatcherOperation? job;
- lock (s_uiThread.InstanceLock)
- job = s_uiThread._queue.Peek();
- if (job == null || job.Priority <= DispatcherPriority.Inactive)
- {
- s_uiThread.ShutdownImpl();
- s_uiThread = null;
- return;
- }
- s_uiThread.ExecuteJob(job);
- }
- }
-
- private void ExecuteJob(DispatcherOperation job)
- {
- lock (InstanceLock)
- _queue.RemoveItem(job);
- job.Execute();
- // The backend might be firing timers with a low priority,
- // so we manually check if our high priority timers are due for execution
- PromoteTimers();
- }
- private void Signaled()
- {
- lock (InstanceLock)
- _signaled = false;
- ExecuteJobsCore(false);
- }
- void ExecuteJobsCore(bool fromExplicitBackgroundProcessingCallback)
- {
- long? backgroundJobExecutionStartedAt = null;
- while (true)
- {
- DispatcherOperation? job;
- lock (InstanceLock)
- job = _queue.Peek();
-
- if (job == null || job.Priority < DispatcherPriority.MinimumActiveValue)
- return;
-
-
- // We don't stop for executing jobs queued with >Input priority
- if (job.Priority > DispatcherPriority.Input)
- {
- ExecuteJob(job);
- }
- // If platform supports pending input query, ask the platform if we can continue running low priority jobs
- else if (_pendingInputImpl?.CanQueryPendingInput == true)
- {
- if (!_pendingInputImpl.HasPendingInput)
- ExecuteJob(job);
- else
- {
- RequestBackgroundProcessing();
- return;
- }
- }
- // We can't ask if the implementation has pending input, so we should let it to call us back
- // Once it thinks that input is handled
- else if (_backgroundProcessingImpl != null && !fromExplicitBackgroundProcessingCallback)
- {
- RequestBackgroundProcessing();
- return;
- }
- // We can't check if there is pending input, but still need to enforce interactivity
- // so we stop processing background jobs after some timeout and start a timer to continue later
- else
- {
- if (backgroundJobExecutionStartedAt == null)
- backgroundJobExecutionStartedAt = Now;
-
- if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime)
- {
- RequestBackgroundProcessing();
- return;
- }
- else
- ExecuteJob(job);
- }
- }
- }
- internal bool RequestProcessing()
- {
- lock (InstanceLock)
- {
- if (!CheckAccess())
- {
- RequestForegroundProcessing();
- return true;
- }
- if (_queue.MaxPriority <= DispatcherPriority.Input)
- {
- if (_pendingInputImpl is { CanQueryPendingInput: true, HasPendingInput: false })
- RequestForegroundProcessing();
- else
- RequestBackgroundProcessing();
- }
- else
- RequestForegroundProcessing();
- }
- return true;
- }
- private void RequestForegroundProcessing()
- {
- if (!_signaled)
- {
- _signaled = true;
- _impl.Signal();
- }
- }
- internal void Abort(DispatcherOperation operation)
- {
- lock (InstanceLock)
- _queue.RemoveItem(operation);
- operation.DoAbort();
- }
-
- // Returns whether or not the priority was set.
- internal bool SetPriority(DispatcherOperation operation, DispatcherPriority priority) // NOTE: should be Priority
- {
- bool notify = false;
- lock(InstanceLock)
- {
- if(operation.IsQueued)
- {
- _queue.ChangeItemPriority(operation, priority);
- notify = true;
- if(notify)
- {
- // Make sure we will wake up to process this operation.
- RequestProcessing();
- }
- }
- }
- return notify;
- }
- public bool HasJobsWithPriority(DispatcherPriority priority)
- {
- lock (InstanceLock)
- return _queue.MaxPriority >= priority;
- }
- /// <summary>
- /// Gets all pending jobs, unordered, without removing them.
- /// </summary>
- /// <remarks>Only use between unit tests!</remarks>
- /// <returns>A list of jobs.</returns>
- internal List<DispatcherOperation> GetJobs()
- {
- lock (InstanceLock)
- return _queue.PeekAll();
- }
- /// <summary>
- /// Clears all pending jobs.
- /// </summary>
- /// <remarks>Only use between unit tests!</remarks>
- internal void ClearJobs()
- {
- lock (InstanceLock)
- _queue.Clear();
- }
- }
|