public async Task YourMethod()
{
using await _semaphore.LockAsync();
// todo
} //the using statement will auto-release the semaphore
扩展方法如下:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace YourNamespace
{
public static class SemaphorSlimExtensions
{
public static IDisposable LockSync(this SemaphoreSlim semaphore)
{
if (semaphore == null)
throw new ArgumentNullException(nameof(semaphore));
var wrapper = new AutoReleaseSemaphoreWrapper(semaphore);
semaphore.Wait();
return wrapper;
}
public static async Task<IDisposable> LockAsync(this SemaphoreSlim semaphore)
{
if (semaphore == null)
throw new ArgumentNullException(nameof(semaphore));
var wrapper = new AutoReleaseSemaphoreWrapper(semaphore);
await semaphore.WaitAsync();
return wrapper;
}
}
}
IDisposable Package 器:
using System;
using System.Threading;
namespace YourNamespace
{
public class AutoReleaseSemaphoreWrapper : IDisposable
{
private readonly SemaphoreSlim _semaphore;
public AutoReleaseSemaphoreWrapper(SemaphoreSlim semaphore )
{
_semaphore = semaphore;
}
public void Dispose()
{
try
{
_semaphore.Release();
}
catch { }
}
}
}
6条答案
按热度按时间vnjpjtjt1#
根据MSDN,
SemaphoreSlim.WaitAsync
可能引发:ObjectDisposedException
-如果已释放信号量ArgumentOutOfRangeException
-如果选择接受int
的重载,并且它是负数(不包括-1)在这两种情况下,
SemaphoreSlim
都不会获取锁,这使得在finally
块中释放它变得不必要。需要注意的一点是,如果在第二个示例中对象被disposed或null,finally块将执行并触发另一个异常或调用
Release
,而Release
可能首先没有获取任何要释放的锁。最后,为了与非异步锁保持一致并避免
finally
块中的异常,我将选择前者vh0rcniy2#
如果我们考虑
ThreadAbortException
,这两个选项都是危险的,在旧的.NET Framework代码中可能会发生异常,尽管需要注意的是,它不会在新的.NET Core代码中发生,正如Microsoft所说:即使.NET Core和.NET 5+中存在此类型,但由于不支持Abort,公共语言运行库将永远不会引发ThreadAbortException。1.考虑Option 1和
ThreadAbortException
发生在WaitAsync
和try
之间。在这种情况下,将获得信号量锁但从未释放。最终将导致死锁。1.现在在Option 2中,如果
ThreadAbortException
发生在获取锁之前,我们仍然会尝试释放其他人的锁,或者如果信号量没有被锁定,我们会得到SemaphoreFullException
。理论上,我们可以使用Option 2来跟踪锁是否被获取,为此,我们将把锁获取和跟踪逻辑放入
finally
块中的另一个(内部)try-finally
语句中,原因是ThreadAbortException
不会中断finally
块的执行,因此我们将得到如下内容:不幸的是,我们仍然不安全。问题是
Thread.Abort
锁定调用线程,直到中止线程离开保护区域(在我们的场景中是内部finally
块)。这可能导致死锁。为了避免无限期或长时间运行的信号量等待,我们可以定期中断它,并给予ThreadAbortException
一个中断执行的机会。现在逻辑感觉安全了。sshcrbum3#
如果
WaitAsync
内部有异常,则信号量是notacquired,因此Release
是不必要的,应该避免,应该使用第一个代码片段。如果您担心在实际获取信号量时出现异常(除了
NullReferenceException
之外,不太可能出现异常),您可以尝试单独捕获它:luaexgnf4#
第一个选项是为了避免在Wait调用抛出的事件中调用release。尽管如此,使用c#8.0我们可以编写一些东西,这样我们就不会在每个需要使用信号量的方法上有那么多难看的嵌套。
用法:
扩展方法如下:
IDisposable Package 器:
q35jwt9p5#
这是对Bill Tarbell的
LockSync
类的SemaphoreSlim
扩展方法的一个尝试性改进。通过使用值类型IDisposable
Package 器和ValueTask
返回类型,可以显著减少SemaphoreSlim
类自身分配之外的额外分配。用法示例(同步/异步):
同步
Lock
始终是无分配的,无论是立即获取信号量还是在阻塞等待之后获取信号量。异步LockAsync
也是无分配的,但仅当同步获取信号量时(当它是CurrentCount
时恰好是正值)。当存在争用并且LockAsync
必须异步完成时,除了标准SemaphoreSlim.WaitAsync
分配之外,还额外分配了144个字节(在64位机器上,从.NET 5开始,不带CancellationToken
时为88个字节,带可取消CancellationToken
时为497个字节)。来自文档:
从C# 7.0开始支持使用
ValueTask<TResult>
类型,任何版本的Visual Basic都不支持。readonly
structs从C# 7.2开始可用。此外,还解释了here为什么
IDisposable
ReleaseToken
结构没有被using
语句装箱。ghhkc1vu6#
这是一个答案和问题的混合体。
来自一篇关于
lock(){}
实现的文章:这里的问题是,如果编译器在监视器输入和try保护区域之间生成了一个no-op指令,那么运行时就有可能在监视器输入之后、try之前抛出一个线程中止异常。在这种情况下,finally永远不会运行,所以锁会泄漏。可能最终导致程序死锁。如果这在未优化和优化的版本中都不可能,那就太好了。(https://blogs.msdn.microsoft.com/ericlippert/2009/03/06/locks-and-exceptions-do-not-mix/)
当然,
lock
是不同的,但是从这篇注解中我们可以得出结论,如果SemaphoreSlim.WaitAsync()
还提供了一种方法来确定锁是否被成功获取(就像本文中描述的Monitor.Enter
那样),那么将SemaphoreSlim.WaitAsync()
放在try
块中可能也会更好,但是SemaphoreSlim
没有提供这样的机制。这篇关于
using
实现的文章说:转换为:
如果noop可以在
Monitor.Enter()
和紧随其后的try
之间生成,那么同样的问题是否也适用于转换后的using
代码?也许这个
AsyncSemaphore
https://github.com/Microsoft/vs-threading/blob/81db9bbc559e641c2b2baf2b811d959f0c0adf24/src/Microsoft.VisualStudio.Threading/AsyncSemaphore.cs的实现以及
SemaphoreSlim
https://github.com/StephenCleary/AsyncEx/blob/02341dbaf3df62e97c4bbaeb6d6606d345f9cda5/src/Nito.AsyncEx.Coordination/SemaphoreSlimExtensions.cs的扩展名也很有趣。