sh1*_*1ng 7 .net c# semaphore locking async-await
我知道在同步世界中第一个片段是正确的,但是WaitAsync和async/await magic是什么?请给我一些.net内部.
await _semaphore.WaitAsync();
try
{
// todo
}
finally
{
_semaphore.Release();
}
Run Code Online (Sandbox Code Playgroud)
要么
try
{
await _semaphore.WaitAsync();
// todo
}
finally
{
_semaphore.Release();
}
}
Run Code Online (Sandbox Code Playgroud)
Yuv*_*kov 13
根据MSDN,SemaphoreSlim.WaitAsync可能会抛出:
ObjectDisposedException - 如果信号量已被处理掉
ArgumentOutOfRangeException- 如果你选择接受a的重载int并且它是负数(不包括-1)
在这两种情况下,都SemaphoreSlim不会获得锁定,这使得将其释放到一个finally区块中是不明智的.
需要注意的一件事是,如果对象在第二个示例中被处理或为空,则finally块将执行并触发另一个异常或调用Release,这可能没有获得任何锁定以在第一个位置释放.
总而言之,我会与前者保持一致,以便与非异步锁定保持一致并避免finally块中的异常
如果没有获取WaitAsync信号量内部的异常,那么 a是不必要的,应该避免。你应该使用第一个片段。Release
如果您担心在实际获取信号量时出现异常(除 之外不太可能NullReferenceException),您可以独立尝试捕获它:
try
{
await _semaphore.WaitAsync();
}
catch
{
// handle
}
try
{
// todo
}
finally
{
_semaphore.Release();
}
Run Code Online (Sandbox Code Playgroud)
如果我们考虑 ,这两种选择都是危险的ThreadAbortException。
ThreadAbortException发生在WaitAsync和之间try。在这种情况下,信号量锁将被获取但永远不会被释放。最终会导致僵局。await _semaphore.WaitAsync();
// ThreadAbortException happens here
try
{
// todo
}
finally
{
_semaphore.Release();
}
Run Code Online (Sandbox Code Playgroud)
ThreadAbortException发生在获得锁之前,我们仍然会尝试释放其他人的锁,或者SemaphoreFullException如果信号量没有锁定我们就会释放。try
{
// ThreadAbortException happens here
await _semaphore.WaitAsync();
// todo
}
finally
{
_semaphore.Release();
}
Run Code Online (Sandbox Code Playgroud)
理论上,我们可以使用选项 2并跟踪是否实际获取了锁。为此,我们将把锁获取和跟踪逻辑放到try-finally一个finally块中的另一个(内部)语句中。原因是ThreadAbortException它不会中断finally块的执行。所以我们会有这样的事情:
var isTaken = false;
try
{
try
{
}
finally
{
await _semaphore.WaitAsync();
isTaken = true;
}
// todo
}
finally
{
if (isTaken)
{
_semaphore.Release();
}
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,我们仍然不安全。问题是它会Thread.Abort锁定调用线程,直到中止线程离开受保护区域(finally我们场景中的内部块)。这可能会导致僵局。为了避免无限或长时间运行的信号量等待,我们可以定期中断它并给ThreadAbortException中断执行的机会。现在这个逻辑感觉很安全。
var isTaken = false;
try
{
do
{
try
{
}
finally
{
isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
}
}
while(!isTaken);
// todo
}
finally
{
if (isTaken)
{
_semaphore.Release();
}
}
Run Code Online (Sandbox Code Playgroud)
This is an attempted improvement of Bill Tarbell's LockSync extension method for the SemaphoreSlim class. By using a value-type IDisposable wrapper and a ValueTask return type, it is possible to reduce significantly the additional allocations beyond what the SemaphoreSlim class allocates by itself.
public static ReleaseToken Lock(this SemaphoreSlim semaphore,
CancellationToken cancellationToken = default)
{
semaphore.Wait(cancellationToken);
return new ReleaseToken(semaphore);
}
public static async ValueTask<ReleaseToken> LockAsync(this SemaphoreSlim semaphore,
CancellationToken cancellationToken = default)
{
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
return new ReleaseToken(semaphore);
}
public readonly struct ReleaseToken : IDisposable
{
private readonly SemaphoreSlim _semaphore;
public ReleaseToken(SemaphoreSlim semaphore) => _semaphore = semaphore;
public void Dispose() => _semaphore?.Release();
}
Run Code Online (Sandbox Code Playgroud)
Usage example (sync/async):
using (semaphore.Lock())
{
DoStuff();
}
using (await semaphore.LockAsync())
{
await DoStuffAsync();
}
Run Code Online (Sandbox Code Playgroud)
同步Lock始终是免分配的,无论信号量是立即获取还是在阻塞等待之后获取。异步LockAsync也是免分配的,但仅当同步获取信号量时(当时恰好CurrentCount为正)。当存在争用且LockAsync必须异步完成时,将在标准分配之外额外分配 144 字节(在 64 位计算机上,SemaphoreSlim.WaitAsync不带 时为 88 字节CancellationToken,从 .NET 5 开始可取消为 497 字节)。CancellationToken
来自文档:
从 C# 7.0 开始支持使用该
ValueTask<TResult>类型,但任何版本的 Visual Basic 均不支持该类型。
readonly 从 C# 7.2 开始,结构体可用。
这里还解释了为什么该IDisposable ReleaseToken结构没有被using语句装箱。
注意:就我个人而言,我不喜欢(错误地)将该using语句用于释放非托管资源以外的目的。
| 归档时间: |
|
| 查看次数: |
3826 次 |
| 最近记录: |