我创建了一个名为
MutexAsyncable
的类,受到Stephen Toub的AsyncLock实现(在
这篇博客文章中讨论)的启发,它可以用作同步或异步代码中
lock
语句的替代品。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace UtilsCommon.Lib;
public sealed class MutexAsyncable {
#region Internal classes
private sealed class Releaser : IDisposable {
private readonly MutexAsyncable _toRelease;
internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; }
public void Dispose() { _toRelease._semaphore.Release(); }
}
#endregion
private readonly SemaphoreSlim _semaphore = new(1, 1);
private readonly Task<IDisposable> _releaser;
public MutexAsyncable() {
_releaser = Task.FromResult((IDisposable)new Releaser(this));
}
public IDisposable LockSync() {
_semaphore.Wait();
return _releaser.Result;
}
public Task<IDisposable> LockAsync() {
var wait = _semaphore.WaitAsync();
if (wait.IsCompleted) { return _releaser; }
else {
return wait.ContinueWith(
(_, state) => (IDisposable)state!,
_releaser.Result,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
);
}
}
}
如果您使用的是.NET 5+,那么使用上述内容是安全的,因为它不会抛出ThreadAbortException
。
我还创建了一个扩展的SemaphoreLocker
类,受this answer启发,它可以作为lock
的通用替代品,可同步或异步使用。它比上面的MutexAsyncable
效率低,并且分配更多资源,但它有一个好处,即强制工作代码在完成后释放锁(从技术上讲,MutexAsyncable
返回的IDisposable
可能无法通过调用代码进行处理并导致死锁)。它还有额外的try/finally代码来处理可能出现的ThreadAbortException
,因此应该可以在早期的.NET版本中使用:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace UtilsCommon.Lib;
public sealed class SemaphoreLocker : IDisposable {
private readonly SemaphoreSlim _semaphore = new(1, 1);
public T LockSync<T>(Func<T> worker) {
var isTaken = false;
try {
do {
try {
}
finally {
isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1));
}
}
while (!isTaken);
return worker();
}
finally {
if (isTaken) {
_semaphore.Release();
}
}
}
public void LockSync(Action worker) {
var isTaken = false;
try {
do {
try {
}
finally {
isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1));
}
}
while (!isTaken);
worker();
}
finally {
if (isTaken) {
_semaphore.Release();
}
}
}
public async Task<T> LockAsync<T>(Func<Task<T>> worker) {
var isTaken = false;
try {
do {
try {
}
finally {
isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
}
}
while (!isTaken);
return await worker();
}
finally {
if (isTaken) {
_semaphore.Release();
}
}
}
public async Task LockAsync(Func<Task> worker) {
var isTaken = false;
try {
do {
try {
}
finally {
isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
}
}
while (!isTaken);
await worker();
}
finally {
if (isTaken) {
_semaphore.Release();
}
}
}
public void Dispose() {
_semaphore.Dispose();
}
}