140 lines
5.1 KiB
C#
140 lines
5.1 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT License.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Collections.Generic;
|
|
|
|
namespace System.Reactive.Concurrency
|
|
{
|
|
/// <summary>
|
|
/// Asynchronous lock.
|
|
/// </summary>
|
|
public sealed class AsyncLock : IDisposable
|
|
{
|
|
private bool _isAcquired;
|
|
private bool _hasFaulted;
|
|
private readonly object _guard = new();
|
|
private Queue<(Action<Delegate, object?> action, Delegate @delegate, object? state)>? _queue;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="action">Action to queue for execution.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
|
|
public void Wait(Action action)
|
|
{
|
|
if (action == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(action));
|
|
}
|
|
|
|
Wait(action, static closureAction => closureAction!());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="action">Action to queue for execution.</param>
|
|
/// <param name="state">The state to pass to the action when it gets invoked under the lock.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
|
|
/// <remarks>In case TState is a value type, this operation will involve boxing of <paramref name="state"/>.
|
|
/// However, this is often an improvement over the allocation of a closure object and a delegate.</remarks>
|
|
internal void Wait<TState>(TState state, Action<TState> action)
|
|
{
|
|
if (action == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(action));
|
|
}
|
|
|
|
Wait(state, action, static (actionObject, stateObject) => ((Action<TState>)actionObject)((TState)stateObject!));
|
|
}
|
|
|
|
private void Wait(object? state, Delegate @delegate, Action<Delegate, object?> 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<(Action<Delegate, object?> action, Delegate @delegate, object? state)>();
|
|
_queue = q;
|
|
}
|
|
// enqueue the work
|
|
q.Enqueue((action, @delegate, state));
|
|
return;
|
|
}
|
|
|
|
// indicate there is processing going on
|
|
_isAcquired = true;
|
|
}
|
|
|
|
// if we get here, execute the "action" first
|
|
|
|
for (; ; )
|
|
{
|
|
try
|
|
{
|
|
action(@delegate, state);
|
|
}
|
|
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, @delegate, state) = q.Dequeue();
|
|
}
|
|
// loop back and execute the action
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the work items in the queue and drops further work being queued.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
lock (_guard)
|
|
{
|
|
_queue = null;
|
|
_hasFaulted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|