using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Xamarin.PropertyEditing { /// /// This is NOT thread safe. It serves purely to keep async-waiting UI events in order without blocking the UI thread. /// The added capability here past just waiting on a task and swapping it with the new wait is child requests will pass /// through. /// internal class AsyncWorkQueue { public Task RequestAsyncWork (object requester) { var worker = new AsyncValueWorker (requester, this); this.workers.AddLast (worker.Node); if (this.workers.Count == 1 || ReferenceEquals (requester, this.activeRequester)) { worker.Completion.SetResult (worker); this.activeRequester = requester; } return worker.Completion.Task; } private object activeRequester; private readonly LinkedList workers = new LinkedList(); private void CompleteWork (AsyncValueWorker worker) { LinkedListNode left = worker.Node.Previous, right = worker.Node.Next; AsyncValueWorker toFree = left?.Value ?? right?.Value; List related = null; // If a worker is still in the queue, it's still working. All children/siblings of the // current requester must be finished before it can move on. while (left != null || right != null) { if (ReferenceEquals (left?.Value.Requester, worker.Requester) || ReferenceEquals (right?.Value.Requester, worker.Requester)) { toFree = null; break; } if (ReferenceEquals (left?.Value.Requester, toFree?.Requester)) { if (related == null) related = new List (); related.Add (left.Value); } if (ReferenceEquals (right?.Value.Requester, toFree?.Requester)) { if (related == null) related = new List (); related.Add (right.Value); } left = left?.Previous; right = right?.Next; } this.workers.Remove (worker); if (toFree != null) { this.activeRequester = toFree.Requester; toFree.Completion.SetResult (toFree); if (related != null) { // Once the active requester changes, we need to sure its children/siblings are all allowed // same as if it had been the active one first. foreach (var w in related) { w.Completion.TrySetResult (w); } } } } private class AsyncValueWorker : IDisposable { public AsyncValueWorker (object requester, AsyncWorkQueue queue) { this.queue = queue; Requester = requester; Node = new LinkedListNode (this); } public object Requester { get; } public LinkedListNode Node { get; } public TaskCompletionSource Completion { get; } = new TaskCompletionSource (); public void Dispose () { if (this.isDisposed) return; this.isDisposed = true; this.queue.CompleteWork (this); } private bool isDisposed; private readonly AsyncWorkQueue queue; } } }