// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace System.Reactive.Concurrency { /// /// Asynchronous lock. /// public sealed class AsyncLock : IDisposable { private object guard = new object(); private Queue queue; private bool isAcquired = false; private bool hasFaulted = false; /// /// Queues the action for execution. If the caller acquires the lock and becomes the owner, /// the queue is processed. If the lock is already owned, the action is queued and will get /// processed by the owner. /// /// Action to queue for execution. /// is null. public void Wait(Action action) { if (action == null) throw new ArgumentNullException(nameof(action)); // allow one thread to update the state lock (guard) { // if a previous action crashed, ignore any future actions if (hasFaulted) { return; } // if the "lock" is busy, queue up the extra work // otherwise there is no need to queue up "action" if (isAcquired) { // create the queue if necessary var q = queue; if (q == null) { q = new Queue(); queue = q; } // enqueue the work q.Enqueue(action); return; } // indicate there is processing going on isAcquired = true; } // if we get here, execute the "action" first for (; ; ) { try { action(); } catch { // the execution failed, terminate this AsyncLock lock (guard) { // throw away the queue queue = null; // report fault hasFaulted = true; } throw; } // execution succeeded, let's see if more work has to be done lock (guard) { var q = queue; // either there is no queue yet or we run out of work if (q == null || q.Count == 0) { // release the lock isAcquired = false; return; } // get the next work action action = q.Dequeue(); } // loop back and execute the action } } /// /// Clears the work items in the queue and drops further work being queued. /// public void Dispose() { lock (guard) { queue = null; hasFaulted = true; } } } }