Dispatcher.Queue.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Threading;
  5. namespace Avalonia.Threading;
  6. public partial class Dispatcher
  7. {
  8. private readonly DispatcherPriorityQueue _queue = new();
  9. private bool _signaled;
  10. private bool _explicitBackgroundProcessingRequested;
  11. private const int MaximumInputStarvationTimeInFallbackMode = 50;
  12. private const int MaximumInputStarvationTimeInExplicitProcessingExplicitMode = 50;
  13. private readonly int _maximumInputStarvationTime;
  14. void RequestBackgroundProcessing()
  15. {
  16. lock (InstanceLock)
  17. {
  18. if (_backgroundProcessingImpl != null)
  19. {
  20. if(_explicitBackgroundProcessingRequested)
  21. return;
  22. _explicitBackgroundProcessingRequested = true;
  23. _backgroundProcessingImpl.RequestBackgroundProcessing();
  24. }
  25. else if (_dueTimeForBackgroundProcessing == null)
  26. {
  27. _dueTimeForBackgroundProcessing = Now + 1;
  28. UpdateOSTimer();
  29. }
  30. }
  31. }
  32. private void OnReadyForExplicitBackgroundProcessing()
  33. {
  34. lock (InstanceLock)
  35. {
  36. _explicitBackgroundProcessingRequested = false;
  37. }
  38. ExecuteJobsCore(true);
  39. }
  40. /// <summary>
  41. /// Force-runs all dispatcher operations ignoring any pending OS events, use with caution
  42. /// </summary>
  43. public void RunJobs(DispatcherPriority? priority = null)
  44. {
  45. RunJobs(priority, CancellationToken.None);
  46. }
  47. internal void RunJobs(DispatcherPriority? priority, CancellationToken cancellationToken)
  48. {
  49. if (DisabledProcessingCount > 0)
  50. throw new InvalidOperationException(
  51. "Cannot perform this operation while dispatcher processing is suspended.");
  52. priority ??= DispatcherPriority.MinimumActiveValue;
  53. if (priority < DispatcherPriority.MinimumActiveValue)
  54. priority = DispatcherPriority.MinimumActiveValue;
  55. while (!cancellationToken.IsCancellationRequested)
  56. {
  57. DispatcherOperation? job;
  58. lock (InstanceLock)
  59. job = _queue.Peek();
  60. if (job == null)
  61. return;
  62. if (job.Priority < priority.Value)
  63. return;
  64. ExecuteJob(job);
  65. }
  66. }
  67. private sealed class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
  68. {
  69. public bool CurrentThreadIsLoopThread => true;
  70. public void Signal()
  71. {
  72. }
  73. public event Action? Signaled
  74. {
  75. add { }
  76. remove { }
  77. }
  78. public event Action? Timer
  79. {
  80. add { }
  81. remove { }
  82. }
  83. public long Now => 0;
  84. public void UpdateTimer(long? dueTimeInMs)
  85. {
  86. }
  87. }
  88. internal static void ResetBeforeUnitTests()
  89. {
  90. s_uiThread = null;
  91. }
  92. internal static void ResetForUnitTests()
  93. {
  94. if (s_uiThread == null)
  95. return;
  96. var st = Stopwatch.StartNew();
  97. while (true)
  98. {
  99. s_uiThread._pendingInputImpl = s_uiThread._controlledImpl = null;
  100. s_uiThread._impl = new DummyShuttingDownUnitTestDispatcherImpl();
  101. if (st.Elapsed.TotalSeconds > 5)
  102. throw new InvalidProgramException("You've caused dispatcher loop");
  103. DispatcherOperation? job;
  104. lock (s_uiThread.InstanceLock)
  105. job = s_uiThread._queue.Peek();
  106. if (job == null || job.Priority <= DispatcherPriority.Inactive)
  107. {
  108. s_uiThread.ShutdownImpl();
  109. s_uiThread = null;
  110. return;
  111. }
  112. s_uiThread.ExecuteJob(job);
  113. }
  114. }
  115. private void ExecuteJob(DispatcherOperation job)
  116. {
  117. lock (InstanceLock)
  118. _queue.RemoveItem(job);
  119. job.Execute();
  120. // The backend might be firing timers with a low priority,
  121. // so we manually check if our high priority timers are due for execution
  122. PromoteTimers();
  123. }
  124. private void Signaled()
  125. {
  126. lock (InstanceLock)
  127. _signaled = false;
  128. ExecuteJobsCore(false);
  129. }
  130. void ExecuteJobsCore(bool fromExplicitBackgroundProcessingCallback)
  131. {
  132. long? backgroundJobExecutionStartedAt = null;
  133. while (true)
  134. {
  135. DispatcherOperation? job;
  136. lock (InstanceLock)
  137. job = _queue.Peek();
  138. if (job == null || job.Priority < DispatcherPriority.MinimumActiveValue)
  139. return;
  140. // We don't stop for executing jobs queued with >Input priority
  141. if (job.Priority > DispatcherPriority.Input)
  142. {
  143. ExecuteJob(job);
  144. }
  145. // If platform supports pending input query, ask the platform if we can continue running low priority jobs
  146. else if (_pendingInputImpl?.CanQueryPendingInput == true)
  147. {
  148. if (!_pendingInputImpl.HasPendingInput)
  149. ExecuteJob(job);
  150. else
  151. {
  152. RequestBackgroundProcessing();
  153. return;
  154. }
  155. }
  156. // We can't ask if the implementation has pending input, so we should let it to call us back
  157. // Once it thinks that input is handled
  158. else if (_backgroundProcessingImpl != null && !fromExplicitBackgroundProcessingCallback)
  159. {
  160. RequestBackgroundProcessing();
  161. return;
  162. }
  163. // We can't check if there is pending input, but still need to enforce interactivity
  164. // so we stop processing background jobs after some timeout and start a timer to continue later
  165. else
  166. {
  167. if (backgroundJobExecutionStartedAt == null)
  168. backgroundJobExecutionStartedAt = Now;
  169. if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime)
  170. {
  171. RequestBackgroundProcessing();
  172. return;
  173. }
  174. else
  175. ExecuteJob(job);
  176. }
  177. }
  178. }
  179. internal bool RequestProcessing()
  180. {
  181. lock (InstanceLock)
  182. {
  183. if (!CheckAccess())
  184. {
  185. RequestForegroundProcessing();
  186. return true;
  187. }
  188. if (_queue.MaxPriority <= DispatcherPriority.Input)
  189. {
  190. if (_pendingInputImpl is { CanQueryPendingInput: true, HasPendingInput: false })
  191. RequestForegroundProcessing();
  192. else
  193. RequestBackgroundProcessing();
  194. }
  195. else
  196. RequestForegroundProcessing();
  197. }
  198. return true;
  199. }
  200. private void RequestForegroundProcessing()
  201. {
  202. if (!_signaled)
  203. {
  204. _signaled = true;
  205. _impl.Signal();
  206. }
  207. }
  208. internal void Abort(DispatcherOperation operation)
  209. {
  210. lock (InstanceLock)
  211. _queue.RemoveItem(operation);
  212. operation.DoAbort();
  213. }
  214. // Returns whether or not the priority was set.
  215. internal bool SetPriority(DispatcherOperation operation, DispatcherPriority priority) // NOTE: should be Priority
  216. {
  217. bool notify = false;
  218. lock(InstanceLock)
  219. {
  220. if(operation.IsQueued)
  221. {
  222. _queue.ChangeItemPriority(operation, priority);
  223. notify = true;
  224. if(notify)
  225. {
  226. // Make sure we will wake up to process this operation.
  227. RequestProcessing();
  228. }
  229. }
  230. }
  231. return notify;
  232. }
  233. public bool HasJobsWithPriority(DispatcherPriority priority)
  234. {
  235. lock (InstanceLock)
  236. return _queue.MaxPriority >= priority;
  237. }
  238. /// <summary>
  239. /// Gets all pending jobs, unordered, without removing them.
  240. /// </summary>
  241. /// <remarks>Only use between unit tests!</remarks>
  242. /// <returns>A list of jobs.</returns>
  243. internal List<DispatcherOperation> GetJobs()
  244. {
  245. lock (InstanceLock)
  246. return _queue.PeekAll();
  247. }
  248. /// <summary>
  249. /// Clears all pending jobs.
  250. /// </summary>
  251. /// <remarks>Only use between unit tests!</remarks>
  252. internal void ClearJobs()
  253. {
  254. lock (InstanceLock)
  255. _queue.Clear();
  256. }
  257. }