using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace IPA.Utilities.Async { /// /// A single-threaded task scheduler that runs all of its tasks on the same thread. /// public class SingleThreadTaskScheduler : TaskScheduler, IDisposable { private readonly Thread runThread = new Thread(ExecuteTasksS); private readonly BlockingCollection tasks = new BlockingCollection(); private readonly CancellationTokenSource exitTokenSource = new CancellationTokenSource(); /// /// Gets whether or not the underlying thread has been started. /// /// Thrown if this object has already been disposed. public bool IsRunning { get { ThrowIfDisposed(); return runThread.IsAlive; } } /// /// Starts the thread that executes tasks scheduled with this /// /// Thrown if this object has already been disposed. public void Start() { ThrowIfDisposed(); runThread.Start(this); } /// /// Terminates the runner thread, and waits for the currently running task to complete. /// /// /// After this method returns, this object has been disposed and is no longer in a valid state. /// /// an of s that did not execute /// Thrown if this object has already been disposed. public IEnumerable Exit() { ThrowIfDisposed(); tasks.CompleteAdding(); exitTokenSource.Cancel(); runThread.Join(); var retTasks = new List(); retTasks.AddRange(tasks); return retTasks; } /// /// Waits for the runner thread to complete all tasks in the queue, then exits. /// /// /// After this method returns, this object has been disposed and is no longer in a valid state. /// /// Thrown if this object has already been disposed. public void Join() { ThrowIfDisposed(); tasks.CompleteAdding(); runThread.Join(); } /// /// Throws a . /// /// nothing /// Always. protected override IEnumerable GetScheduledTasks() { // this is only for debuggers which we can't use sooooo throw new NotSupportedException(); } /// /// Queues a given to this scheduler. The must> be /// scheduled for this by the runtime. /// /// the to queue /// Thrown if this object has already been disposed. protected override void QueueTask(Task task) { ThrowIfDisposed(); tasks.Add(task); } /// /// Rejects any attempts to execute a task inline. /// /// /// This task scheduler always runs its tasks on the thread that it manages, therefore it doesn't /// make sense to run it inline. /// /// the task to attempt to execute /// whether the task was previously queued to this scheduler /// /// Thrown if this object has already been disposed. protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { ThrowIfDisposed(); return false; } private void ThrowIfDisposed() { if (disposedValue) throw new ObjectDisposedException(nameof(SingleThreadTaskScheduler)); } private void ExecuteTasks() { ThrowIfDisposed(); var token = exitTokenSource.Token; try { // while we are still accepting tasks, and we can pull out a task with an infinite wait duration while (!tasks.IsCompleted && tasks.TryTake(out var task, -1, token)) { TryExecuteTask(task); } } catch (OperationCanceledException) { // TryTake was cancelled, we'll just leave } } private static void ExecuteTasksS(object param) { var self = param as SingleThreadTaskScheduler; self.ExecuteTasks(); } #region IDisposable Support private bool disposedValue = false; // To detect redundant calls /// /// Disposes this object. /// /// whether or not to dispose managed objects protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { exitTokenSource.Dispose(); tasks.Dispose(); } disposedValue = true; } } /// /// Disposes this object. This puts the object into an unusable state. /// public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); } #endregion } }