Dispatcher.Queue.cs 7.8 KB

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