Dispatcher.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Avalonia.Platform;
  7. namespace Avalonia.Threading
  8. {
  9. /// <summary>
  10. /// Provides services for managing work items on a thread.
  11. /// </summary>
  12. /// <remarks>
  13. /// In Avalonia, there is usually only a single <see cref="Dispatcher"/> in the application -
  14. /// the one for the UI thread, retrieved via the <see cref="UIThread"/> property.
  15. /// </remarks>
  16. public class Dispatcher : IDispatcher
  17. {
  18. private readonly JobRunner _jobRunner;
  19. private IPlatformThreadingInterface _platform;
  20. public static Dispatcher UIThread { get; } =
  21. new Dispatcher(AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>());
  22. public Dispatcher(IPlatformThreadingInterface platform)
  23. {
  24. _platform = platform;
  25. _jobRunner = new JobRunner(platform);
  26. if (_platform != null)
  27. {
  28. _platform.Signaled += _jobRunner.RunJobs;
  29. }
  30. }
  31. /// <summary>
  32. /// Checks that the current thread is the UI thread.
  33. /// </summary>
  34. public bool CheckAccess() => _platform?.CurrentThreadIsLoopThread ?? true;
  35. /// <summary>
  36. /// Checks that the current thread is the UI thread and throws if not.
  37. /// </summary>
  38. /// <exception cref="InvalidOperationException">
  39. /// The current thread is not the UI thread.
  40. /// </exception>
  41. public void VerifyAccess()
  42. {
  43. if (!CheckAccess())
  44. throw new InvalidOperationException("Call from invalid thread");
  45. }
  46. /// <summary>
  47. /// Runs the dispatcher's main loop.
  48. /// </summary>
  49. /// <param name="cancellationToken">
  50. /// A cancellation token used to exit the main loop.
  51. /// </param>
  52. public void MainLoop(CancellationToken cancellationToken)
  53. {
  54. var platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
  55. cancellationToken.Register(() => platform.Signal(DispatcherPriority.Send));
  56. platform.RunLoop(cancellationToken);
  57. }
  58. /// <summary>
  59. /// Runs continuations pushed on the loop.
  60. /// </summary>
  61. public void RunJobs()
  62. {
  63. _jobRunner.RunJobs(null);
  64. }
  65. /// <summary>
  66. /// Use this method to ensure that more prioritized tasks are executed
  67. /// </summary>
  68. /// <param name="minimumPriority"></param>
  69. public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority);
  70. /// <inheritdoc/>
  71. public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
  72. {
  73. Contract.Requires<ArgumentNullException>(action != null);
  74. return _jobRunner.InvokeAsync(action, priority);
  75. }
  76. /// <inheritdoc/>
  77. public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
  78. {
  79. Contract.Requires<ArgumentNullException>(function != null);
  80. return _jobRunner.InvokeAsync(function, priority);
  81. }
  82. /// <inheritdoc/>
  83. public Task InvokeAsync(Func<Task> function, DispatcherPriority priority = DispatcherPriority.Normal)
  84. {
  85. Contract.Requires<ArgumentNullException>(function != null);
  86. return _jobRunner.InvokeAsync(function, priority).Unwrap();
  87. }
  88. /// <inheritdoc/>
  89. public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> function, DispatcherPriority priority = DispatcherPriority.Normal)
  90. {
  91. Contract.Requires<ArgumentNullException>(function != null);
  92. return _jobRunner.InvokeAsync(function, priority).Unwrap();
  93. }
  94. /// <inheritdoc/>
  95. public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
  96. {
  97. Contract.Requires<ArgumentNullException>(action != null);
  98. _jobRunner.Post(action, priority);
  99. }
  100. /// <summary>
  101. /// This is needed for platform backends that don't have internal priority system (e. g. win32)
  102. /// To ensure that there are no jobs with higher priority
  103. /// </summary>
  104. /// <param name="currentPriority"></param>
  105. internal void EnsurePriority(DispatcherPriority currentPriority)
  106. {
  107. if (currentPriority == DispatcherPriority.MaxValue)
  108. return;
  109. currentPriority += 1;
  110. _jobRunner.RunJobs(currentPriority);
  111. }
  112. /// <summary>
  113. /// Allows unit tests to change the platform threading interface.
  114. /// </summary>
  115. internal void UpdateServices()
  116. {
  117. if (_platform != null)
  118. {
  119. _platform.Signaled -= _jobRunner.RunJobs;
  120. }
  121. _platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
  122. _jobRunner.UpdateServices();
  123. if (_platform != null)
  124. {
  125. _platform.Signaled += _jobRunner.RunJobs;
  126. }
  127. }
  128. }
  129. }