Kit.Core/LibExternal/System.Reactive/Concurrency/AsyncLock.cs

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;
}
}
}
}