using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace LeanCloud.Storage.Internal { /// /// A helper class for enqueuing tasks /// public class TaskQueue { /// /// We only need to keep the tail of the queue. Cancelled tasks will /// just complete normally/immediately when their turn arrives. /// private Task tail; private readonly object mutex = new object(); /// /// Gets a cancellable task that can be safely awaited and is dependent /// on the current tail of the queue. This essentially gives us a proxy /// for the tail end of the queue whose awaiting can be cancelled. /// /// A cancellation token that cancels /// the task even if the task is still in the queue. This allows the /// running task to return immediately without breaking the dependency /// chain. It also ensures that errors do not propagate. /// A new task that should be awaited by enqueued tasks. private Task GetTaskToAwait(CancellationToken cancellationToken) { lock (mutex) { Task toAwait = tail ?? Task.FromResult(true); return toAwait.ContinueWith(task => { }, cancellationToken); } } /// /// Enqueues a task created by . If the task is /// cancellable (or should be able to be cancelled while it is waiting in the /// queue), pass a cancellationToken. /// /// The type of task. /// A function given a task to await once state is /// snapshotted (e.g. after capturing session tokens at the time of the save call). /// Awaiting this task will wait for the created task's turn in the queue. /// A cancellation token that can be used to /// cancel waiting in the queue. /// The task created by the taskStart function. public T Enqueue(Func taskStart, CancellationToken cancellationToken) where T : Task { Task oldTail; T task; lock (mutex) { oldTail = this.tail ?? Task.FromResult(true); // The task created by taskStart is responsible for waiting the // task passed to it before doing its work (this gives it an opportunity // to do startup work or save state before waiting for its turn in the queue task = taskStart(GetTaskToAwait(cancellationToken)); // The tail task should be dependent on the old tail as well as the newly-created // task. This prevents cancellation of the new task from causing the queue to run // out of order. this.tail = Task.WhenAll(oldTail, task); } return task; } public object Mutex { get { return mutex; } } } }