You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
6.3 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Concurrent;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. namespace IPA.Utilities.Async
  9. {
  10. /// <summary>
  11. /// A single-threaded task scheduler that runs all of its tasks on the same thread.
  12. /// </summary>
  13. public class SingleThreadTaskScheduler : TaskScheduler, IDisposable
  14. {
  15. private readonly Thread runThread = new Thread(ExecuteTasksS);
  16. private readonly BlockingCollection<Task> tasks = new BlockingCollection<Task>();
  17. private readonly CancellationTokenSource exitTokenSource = new CancellationTokenSource();
  18. /// <summary>
  19. /// Gets whether or not the underlying thread has been started.
  20. /// </summary>
  21. /// <exception cref="ObjectDisposedException">Thrown if this object has already been disposed.</exception>
  22. public bool IsRunning
  23. {
  24. get
  25. {
  26. ThrowIfDisposed();
  27. return runThread.IsAlive;
  28. }
  29. }
  30. /// <summary>
  31. /// Starts the thread that executes tasks scheduled with this <see cref="TaskScheduler"/>
  32. /// </summary>
  33. /// <exception cref="ObjectDisposedException">Thrown if this object has already been disposed.</exception>
  34. public void Start()
  35. {
  36. ThrowIfDisposed();
  37. runThread.Start(this);
  38. }
  39. /// <summary>
  40. /// Terminates the runner thread, and waits for the currently running task to complete.
  41. /// </summary>
  42. /// <remarks>
  43. /// After this method returns, this object has been disposed and is no longer in a valid state.
  44. /// </remarks>
  45. /// <returns>an <see cref="IEnumerable{T}"/> of <see cref="Task"/>s that did not execute</returns>
  46. /// <exception cref="ObjectDisposedException">Thrown if this object has already been disposed.</exception>
  47. public IEnumerable<Task> Exit()
  48. {
  49. ThrowIfDisposed();
  50. tasks.CompleteAdding();
  51. exitTokenSource.Cancel();
  52. runThread.Join();
  53. var retTasks = new List<Task>();
  54. retTasks.AddRange(tasks);
  55. return retTasks;
  56. }
  57. /// <summary>
  58. /// Waits for the runner thread to complete all tasks in the queue, then exits.
  59. /// </summary>
  60. /// <remarks>
  61. /// After this method returns, this object has been disposed and is no longer in a valid state.
  62. /// </remarks>
  63. /// <exception cref="ObjectDisposedException">Thrown if this object has already been disposed.</exception>
  64. public void Join()
  65. {
  66. ThrowIfDisposed();
  67. tasks.CompleteAdding();
  68. runThread.Join();
  69. }
  70. /// <summary>
  71. /// Throws a <see cref="NotSupportedException"/>.
  72. /// </summary>
  73. /// <returns>nothing</returns>
  74. /// <exception cref="NotSupportedException">Always.</exception>
  75. protected override IEnumerable<Task> GetScheduledTasks()
  76. {
  77. // this is only for debuggers which we can't use sooooo
  78. throw new NotSupportedException();
  79. }
  80. /// <summary>
  81. /// Queues a given <see cref="Task"/> to this scheduler. The <see cref="Task"/> <i>must></i> be
  82. /// scheduled for this <see cref="TaskScheduler"/> by the runtime.
  83. /// </summary>
  84. /// <param name="task">the <see cref="Task"/> to queue</param>
  85. /// <exception cref="ObjectDisposedException">Thrown if this object has already been disposed.</exception>
  86. protected override void QueueTask(Task task)
  87. {
  88. ThrowIfDisposed();
  89. tasks.Add(task);
  90. }
  91. /// <summary>
  92. /// Rejects any attempts to execute a task inline.
  93. /// </summary>
  94. /// <remarks>
  95. /// This task scheduler <i>always</i> runs its tasks on the thread that it manages, therefore it doesn't
  96. /// make sense to run it inline.
  97. /// </remarks>
  98. /// <param name="task">the task to attempt to execute</param>
  99. /// <param name="taskWasPreviouslyQueued">whether the task was previously queued to this scheduler</param>
  100. /// <returns><see langword="false"/></returns>
  101. /// <exception cref="ObjectDisposedException">Thrown if this object has already been disposed.</exception>
  102. protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
  103. {
  104. ThrowIfDisposed();
  105. return false;
  106. }
  107. private void ThrowIfDisposed()
  108. {
  109. if (disposedValue)
  110. throw new ObjectDisposedException(nameof(SingleThreadTaskScheduler));
  111. }
  112. private void ExecuteTasks()
  113. {
  114. ThrowIfDisposed();
  115. var token = exitTokenSource.Token;
  116. try
  117. {
  118. // while we are still accepting tasks, and we can pull out a task with an infinite wait duration
  119. while (!tasks.IsCompleted && tasks.TryTake(out var task, -1, token))
  120. {
  121. TryExecuteTask(task);
  122. }
  123. }
  124. catch (OperationCanceledException)
  125. {
  126. // TryTake was cancelled, we'll just leave
  127. }
  128. }
  129. private static void ExecuteTasksS(object param)
  130. {
  131. var self = param as SingleThreadTaskScheduler;
  132. self.ExecuteTasks();
  133. }
  134. #region IDisposable Support
  135. private bool disposedValue = false; // To detect redundant calls
  136. /// <summary>
  137. /// Disposes this object.
  138. /// </summary>
  139. /// <param name="disposing">whether or not to dispose managed objects</param>
  140. protected virtual void Dispose(bool disposing)
  141. {
  142. if (!disposedValue)
  143. {
  144. if (disposing)
  145. {
  146. exitTokenSource.Dispose();
  147. tasks.Dispose();
  148. }
  149. disposedValue = true;
  150. }
  151. }
  152. /// <summary>
  153. /// Disposes this object. This puts the object into an unusable state.
  154. /// </summary>
  155. public void Dispose()
  156. {
  157. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  158. Dispose(true);
  159. }
  160. #endregion
  161. }
  162. }