我想知道我是否不必实现可以在某个线程上进入的读取器/写入器锁,然后进行异步,并在不同的线程上发布。
模式是这样的:
ReaderWriterLockSlim syncLock
= new ReaderWriterLockSlim(
LockRecursionPolicy.NoRecursion);
bool gotLock = false;
try {
gotLock = syncLock.TryEnterReadLock(lockMillisecondsTimeout);
if (!gotLock)
throw new TimeoutException();
await foo()
.ConfigureAwait(false);
} finally {
if (gotLock)
syncLock.ExitReadLock();
}
或者:
using (Disposable_Acquires_And_Releases_Lock) {
await foo()
.ConfigureAwait(false);
}
关键是延续回到另一个线程并且无法释放锁定。我发现没有立即实施;也许这是因为它可能有点毛茸茸,确保你实际上在异步分支面前释放锁。
答案 0 :(得分:0)
编辑:这个类正在通过测试并且运行得很好,所以我添加了一些小的改进。对Volatile和Interlocked的调用将被删除,因为它们不是必需的。这非常简单,并且很容易通过状态下的单个监视器来推理......
虽然AsyncEx看起来非常好,但我自己写了一个更简单的锁。我会在这里发布。
这个锁很简单:只有一个双重检查的Monitor
被锁定到设置状态。缺点可能是您需要获取监视器才能进入,并再次退出。它也不是SinWait
,因为它需要获取和脉冲监视器来向服务员发出信号。但实施很简单。
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Sc.Util.System;
namespace Sc.Threading
{
/// <summary>
/// A thred-free reader/writer lock. This lock is NOT recursive.
/// </summary>
public sealed class AsyncReaderWriterLock
{
private static int waitTime
=> 15;
private readonly object handle = new object();
private int readerCount;
private bool hasWriter;
private bool enter(int millisecondsTimeout, CancellationToken cancellationToken, bool isWriteLock)
{
Stopwatch stopwatch = Stopwatch.StartNew();
bool IsTimedOut()
{
if (cancellationToken.IsCancellationRequested)
return true;
if (millisecondsTimeout < 0)
return false;
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds >= millisecondsTimeout)
return true;
millisecondsTimeout -= (int)stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
return false;
}
while (true) {
bool gotLock = false;
try {
if (cancellationToken.CanBeCanceled) {
Monitor.TryEnter(
handle,
(millisecondsTimeout < 0)
? AsyncReaderWriterLock.waitTime
: Math.Min(
AsyncReaderWriterLock.waitTime,
millisecondsTimeout),
ref gotLock);
} else
Monitor.TryEnter(handle, millisecondsTimeout, ref gotLock);
if (!gotLock
|| hasWriter
|| (isWriteLock
&& (readerCount > 0))) {
if (IsTimedOut())
return false;
continue;
}
if (isWriteLock)
hasWriter = true;
else
++readerCount;
return true;
} finally {
if (gotLock) {
Monitor.Pulse(handle);
Monitor.Exit(handle);
}
}
}
}
private void exit(bool isWriteLock)
{
lock (handle) {
if (isWriteLock)
hasWriter = false;
else
--readerCount;
Monitor.Pulse(handle);
}
}
private Task<T> getExitContinuation<T>(Task<T> task, bool isWriteLock)
=> task.ContinueWith(
(argumentTask, exitWriteLock) =>
{
exit((bool)exitWriteLock);
return argumentTask.Result;
},
isWriteLock,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
/// <summary>
/// Tries to enter the reader lock, and creates a disposable object that exits the lock
/// when disposed.
/// </summary>
/// <param name="gotLock">Will be false if your attempt to acquire the lock fails: if false
/// then you do not have the lock.</param>
/// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
/// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
/// <param name="cancellationToken">Optional token that will cancel the wait for the lock.</param>
/// <returns>Not null.</returns>
public IDisposable EnterReaderLock(
out bool gotLock,
int millisecondsTimeout = 1000 * 60 * 3,
CancellationToken cancellationToken = default)
{
DelegateDisposable<bool> result
= DelegateDisposable.With(
() => enter(millisecondsTimeout, cancellationToken, false),
isGotLock =>
{
if (isGotLock)
exit(false);
});
gotLock = result.State;
return result;
}
/// <summary>
/// Tries to enter the reader lock, and creates a Task continuation that exits the lock when
/// your Task is complete.
/// </summary>
/// <param name="task">Not null. This will only be invoked if the lock is acquired; and if
/// so, then the returned task is a continuation of this one that invokes and returns
/// this Tasks's result after exiting the lock.</param>
/// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
/// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
/// <param name="cancellationToken">Optional token that will cancel the wait for the lock.
/// Notice that this token is not used on the Tasks.</param>
/// <returns>You must test the result. The bool will be false if your attempt to acquire the lock
/// fails: if false then you do not have the lock; and the returned task will not be null, and is
/// canceled. If true then the returned task must be awaited for the lock exit and the completion
/// of your task.</returns>
public (bool gotLock, Task<T> task) WithReaderLock<T>(
Func<Task<T>> task,
int millisecondsTimeout = 1000 * 60 * 3,
CancellationToken cancellationToken = default)
=> enter(millisecondsTimeout, cancellationToken, false)
? (true, getExitContinuation(task(), false))
: (false, Task.FromCanceled<T>(default));
/// <summary>
/// Tries to enter the write lock, and creates a disposable object that exits the lock
/// when disposed.
/// </summary>
/// <param name="gotLock">Will be false if your attempt to acquire the lock fails: if false
/// then you do not have the lock.</param>
/// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
/// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
/// <param name="cancellationToken">Optional token that will cancel the wait for the lock.</param>
/// <returns>Not null.</returns>
public IDisposable EnterWriteLock(
out bool gotLock,
int millisecondsTimeout = 1000 * 60 * 3,
CancellationToken cancellationToken = default)
{
DelegateDisposable<bool> result
= DelegateDisposable.With(
() => enter(millisecondsTimeout, cancellationToken, true),
isGotLock =>
{
if (isGotLock)
exit(true);
});
gotLock = result.State;
return result;
}
/// <summary>
/// Tries to enter the write lock, and creates a Task continuation that exits the lock when
/// your Task is complete.
/// </summary>
/// <param name="task">Not null. This will only be invoked if the lock is acquired; and if
/// so, then the returned task is a continuation of this one that invokes and returns
/// this Tasks's result after exiting the lock.</param>
/// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
/// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
/// <param name="cancellationToken">Optional token that will cancel the wait for the lock.
/// Notice that this token is not used on the Tasks.</param>
/// <returns>You must test the result. The bool will be false if your attempt to acquire the lock
/// fails: if false then you do not have the lock; and the returned task will not be null, and is
/// canceled. If true then the returned task must be awaited for the lock exit and the completion
/// of your task.</returns>
public (bool gotLock, Task<T> task) WithWriteLock<T>(
Func<Task<T>> task,
int millisecondsTimeout = 1000 * 60 * 3,
CancellationToken cancellationToken = default)
=> enter(millisecondsTimeout, cancellationToken, true)
? (true, getExitContinuation(task(), true))
: (false, Task.FromCanceled<T>(default));
}
}
...而且包含在Dispose中调用的Action的DelegateDisposable ---是这样的:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Sc.Util.System
{
/// <summary>
/// Simple thread-safe <see cref="IDisposable"/> class that invokes an
/// <see cref="Action"/> in <see cref="IDisposable.Dispose"/>. Will not invoke the
/// delegate twice.
/// </summary>
public sealed class DelegateDisposable
: IDisposable
{
/// <summary>
/// As with <see cref="DelegateDisposable"/>; but this class implements a finalizer.
/// </summary>
public sealed class Finalizing
: IDisposable
{
private Action<bool> disposeDelegate;
/// <summary>
/// Cnstructor.
/// </summary>
/// <param name="dispose">Not null.</param>
public Finalizing(Action<bool> dispose)
=> disposeDelegate = dispose ?? throw new ArgumentNullException(nameof(dispose));
private void dispose(bool isDisposing)
=> Interlocked.Exchange(ref disposeDelegate, null)
?.Invoke(isDisposing);
public void Dispose()
{
GC.SuppressFinalize(this);
dispose(true);
}
~Finalizing()
=> dispose(false);
}
/// <summary>
/// Static constructor method. Invokes your <see cref="Action"/> in
/// <see cref="IDisposable.Dispose"/>. Will not invoke the delegate twice.
/// </summary>
/// <param name="dispose">Not null.</param>
/// <returns>Not null.</returns>
public static IDisposable With(Action dispose)
=> new DelegateDisposable(dispose);
/// <summary>
/// Static constructor method for an instance that implements a finalizer. Invokes your
/// <see cref="Action"/> in <see cref="IDisposable.Dispose"/> or the finalizer. Will not
/// invoke the delegate twice.
/// </summary>
/// <param name="dispose">Not null.</param>
/// <returns>Not null.</returns>
public static IDisposable With(Action<bool> dispose)
=> new Finalizing(dispose);
/// <summary>
/// Static constructor method. Creates a new <see cref="Task"/> that will complete when the
/// returned object is <see cref="IDisposable.Dispose"/>. Also will cancel the task if you
/// provide a token; and allows a Task result.
/// </summary>
/// <param name="task">The task that will complete when the result is disposed.</param>
/// <param name="taskResult">Optional. Will set the result of the completed <c>task</c>.</param>
/// <param name="cancellationToken">Optional token that can cancel the returned <c>task</c>.</param>
/// <returns>Not null.</returns>
public static IDisposable When<T>(
out Task<T> task,
Func<T> taskResult = default,
CancellationToken cancellationToken = default)
{
DelegateDisposable<(TaskCompletionSource<T> tcs, Func<T> taskResult, Action<object> tokenCallback)> result
= DelegateDisposable.With
<(TaskCompletionSource<T> tcs, Func<T> taskResult, Action<object> tokenCallback),
Func<T>>(
taskResultDelegate =>
{
TaskCompletionSource<T> taskCompletionSource = new TaskCompletionSource<T>();
if (!cancellationToken.CanBeCanceled)
return (taskCompletionSource, taskResultDelegate, null);
// ReSharper disable once ConvertToLocalFunction
Action<object> tokenCallback = tcs => ((TaskCompletionSource<T>)tcs).TrySetCanceled();
cancellationToken.Register(tokenCallback, taskCompletionSource);
if (cancellationToken.IsCancellationRequested)
taskCompletionSource.TrySetCanceled();
return (taskCompletionSource, taskResultDelegate, tokenCallback);
},
taskResult ?? (() => default),
tuple =>
{
if (!cancellationToken.CanBeCanceled
|| !cancellationToken.IsCancellationRequested)
tuple.tcs.TrySetResult(tuple.taskResult());
});
task = result.State.tcs.Task;
return result;
}
/// <summary>
/// Static constructor method. This generic class adds a <see cref="DelegateDisposable{T}.State"/>
/// object that you can provide from a delegate when this object is constructed. Invokes your
/// <see cref="Action"/> in <see cref="IDisposable.Dispose"/>. Will not invoke the delegate twice.
/// </summary>
/// <param name="constructor">Not null.</param>
/// <param name="dispose">Not null.</param>
/// <returns>Not null.</returns>
public static DelegateDisposable<TState> With<TState>(Func<TState> constructor, Action<TState> dispose)
=> new DelegateDisposable<TState>(constructor, dispose);
/// <summary>
/// Static constructor method. This this generic class adds a <see cref="DelegateDisposable{T}.State"/>
/// object that you can provide from a delegate when this object is constructed; and this method
/// allows your constructor delegate to provide itself an argument. Invokes your <see cref="Action"/>
/// in <see cref="IDisposable.Dispose"/>. Will not invoke the delegate twice.
/// </summary>
/// <param name="constructor">Not null.</param>
/// <param name="constructorArg">An arbitrary argument for your own <c>constructor</c>.</param>
/// <param name="dispose">Not null.</param>
/// <returns>Not null.</returns>
public static DelegateDisposable<TState> With<TState, TArg>(
Func<TArg, TState> constructor,
TArg constructorArg,
Action<TState> dispose)
=> new DelegateDisposable<TState>(() => constructor(constructorArg), dispose);
/// <summary>
/// Static constructor method for an instance that implements a finalizer.This generic class adds a
/// <see cref="DelegateDisposable{T}.State"/> object that you can provide from a delegate when this
/// object is constructed. Invokes your <see cref="Action"/> in <see cref="IDisposable.Dispose"/>
/// or the finalizer. Will not invoke the delegate twice.
/// </summary>
/// <param name="constructor">Not null.</param>
/// <param name="dispose">Not null.</param>
/// <returns>Not null.</returns>
public static DelegateDisposable<TState>.Finalizing With<TState>(
Func<TState> constructor,
Action<TState, bool> dispose)
=> new DelegateDisposable<TState>.Finalizing(constructor, dispose);
/// <summary>
/// Static constructor method for an instance that implements a finalizer. This this generic class
/// adds a <see cref="DelegateDisposable{T}.State"/> object that you can provide from a delegate when
/// this object is constructed; and this method allows your constructor delegate to provide itself
/// an argument. Invokes your <see cref="Action"/> in <see cref="IDisposable.Dispose"/> or the
/// finalizer. Will not invoke the delegate twice.
/// </summary>
/// <param name="constructor">Not null.</param>
/// <param name="constructorArg">An arbitrary argument for your own <c>constructor</c>.</param>
/// <param name="dispose">Not null.</param>
/// <returns>Not null.</returns>
public static DelegateDisposable<TState>.Finalizing With<TState, TArg>(
Func<TArg, TState> constructor,
TArg constructorArg,
Action<TState, bool> dispose)
=> new DelegateDisposable<TState>.Finalizing(() => constructor(constructorArg), dispose);
private Action disposeDelegate;
/// <summary>
/// Cnstructor.
/// </summary>
/// <param name="dispose">Not null.</param>
public DelegateDisposable(Action dispose)
=> disposeDelegate = dispose ?? throw new ArgumentNullException(nameof(dispose));
public void Dispose()
=> Interlocked.Exchange(ref disposeDelegate, null)
?.Invoke();
}
/// <summary>
/// Simple thread-safe <see cref="IDisposable"/> class that invokes an
/// <see cref="Action"/> in <see cref="IDisposable.Dispose"/>. Will not invoke the
/// delegate twice. This generic class adds a <see cref="State"/> object that you
/// can provide from a delegate when this object is constructed.
/// </summary>
/// <typeparam name="TState">Your state type.</typeparam>
public sealed class DelegateDisposable<TState>
: IDisposable
{
/// <summary>
/// As with <see cref="DelegateDisposable{T}"/>; but this class implements a finalizer.
/// </summary>
public sealed class Finalizing
: IDisposable
{
private Action<TState, bool> disposeDelegate;
/// <summary>
/// Cnstructor.
/// </summary>
/// <param name="constructor">Not null.</param>
/// <param name="dispose">Not null.</param>
public Finalizing(Func<TState> constructor, Action<TState, bool> dispose)
{
if (constructor == null)
throw new ArgumentNullException(nameof(constructor));
// ReSharper disable once JoinNullCheckWithUsage
if (dispose == null)
throw new ArgumentNullException(nameof(dispose));
State = constructor();
disposeDelegate = dispose;
}
/// <summary>
/// Your arbitrary state. Set in the constructor; and set to default in dispose.
/// </summary>
public TState State { get; private set; }
private void dispose(bool isDisposing)
{
Interlocked.Exchange(ref disposeDelegate, null)
?.Invoke(State, isDisposing);
State = default;
}
public void Dispose()
{
GC.SuppressFinalize(this);
dispose(true);
}
~Finalizing()
=> dispose(false);
}
private Action<TState> disposeDelegate;
/// <summary>
/// Cnstructor.
/// </summary>
/// <param name="constructor">Not null.</param>
/// <param name="dispose">Not null.</param>
public DelegateDisposable(Func<TState> constructor, Action<TState> dispose)
{
if (constructor == null)
throw new ArgumentNullException(nameof(constructor));
// ReSharper disable once JoinNullCheckWithUsage
if (dispose == null)
throw new ArgumentNullException(nameof(dispose));
State = constructor();
disposeDelegate = dispose;
}
/// <summary>
/// Your arbitrary state. Set in the constructor; and set to default in dispose.
/// </summary>
public TState State { get; private set; }
public void Dispose()
{
Interlocked.Exchange(ref disposeDelegate, null)
?.Invoke(State);
State = default;
}
}
}