C# try块之前/之后的信号量lim.WaitAsync
我知道在同步世界中,第一个代码片段是正确的,但是WaitAsync和async/await magic是什么呢?请给我一些.net内部组件C# try块之前/之后的信号量lim.WaitAsync,c#,.net,locking,async-await,semaphore,C#,.net,Locking,Async Await,Semaphore,我知道在同步世界中,第一个代码片段是正确的,但是WaitAsync和async/await magic是什么呢?请给我一些.net内部组件 await _semaphore.WaitAsync(); try { // todo } finally { _semaphore.Release(); } 或 如果WaitAsync内部存在异常,则信号量未被获取,因此释放是不必要的,应该避免。您应该使用第一个片段 如果您担心在实际获取信号量时出现异常(除了NullReferenceEx
await _semaphore.WaitAsync();
try
{
// todo
}
finally
{
_semaphore.Release();
}
或
如果
WaitAsync
内部存在异常,则信号量未被获取,因此释放
是不必要的,应该避免。您应该使用第一个片段
如果您担心在实际获取信号量时出现异常(除了NullReferenceException
之外,不太可能出现异常),您可以尝试单独捕获它:
try
{
await _semaphore.WaitAsync();
}
catch
{
// handle
}
try
{
// todo
}
finally
{
_semaphore.Release();
}
根据MSDN,
信号量lim.WaitAsync
可能抛出:
ObjectDisposedException
-如果信号量已被释放ArgumentOutOfRangeException
-如果选择接受int
且为负数的重载(不包括-1)SemaphoreSlim
都不会获取锁,这使得在最后
块中释放锁是不必要的
需要注意的一点是,如果在第二个示例中对象被释放或为null,那么finally块将执行,并触发另一个异常或调用Release
,该异常或调用一开始可能没有获取任何要释放的锁
最后,我将使用前者,以确保与非异步锁的一致性,并避免
最后
块中的异常这是一个答案和一个问题的混合体
来自一篇关于lock(){}
实现的文章:
这里的问题是,如果编译器在监视器enter和try保护区域之间生成no-op指令,则运行时可能在监视器enter之后但在try之前抛出线程中止异常。在这种情况下,finally从未运行,因此锁泄漏,可能最终导致程序死锁。如果这在未优化和优化的构建中是不可能的,那就太好了。
()
当然,lock
是不一样的,但是从本说明中我们可以得出结论,如果它还提供了一种确定锁是否成功获取的方法,那么最好将SemaphoreSlim.WaitAsync()
放在try
块中(如本文所述,Monitor.Enter
)。但是,SemaphoreSlim
不提供这种机制
这篇关于使用实现的文章说:
using (Font font1 = new Font("Arial", 10.0f))
{
byte charset = font1.GdiCharSet;
}
转换为:
{
Font font1 = new Font("Arial", 10.0f);
try
{
byte charset = font1.GdiCharSet;
}
finally
{
if (font1 != null)
((IDisposable)font1).Dispose();
}
}
如果可以在监视器.Enter()
和紧跟其后的try
之间生成noop,那么使用代码转换的也会遇到同样的问题吗
可能是AsyncSemaphore
和信号量LIM的扩展
也很有趣。您的第一个选择是在等待调用抛出时避免调用release。尽管如此,使用c#8.0我们可以编写一些东西,这样我们就不会在每个方法上有太多需要使用信号量的丑陋嵌套
用法:
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;
}
}
}
如果我们考虑ThreadAbortException
,这两个选项都是危险的
考虑选项1和ThreadAbortException
发生在WaitAsync
和try
之间。在这种情况下,将获取信号量锁,但永远不会释放。最终这将导致僵局
现在在选项2中,如果ThreadAbortException
在获得锁之前发生,我们仍然会尝试释放其他人的锁,或者如果信号量未锁定,我们将获得SemaphoreLexException
理论上,我们可以使用选项2跟踪是否实际获取了锁。为此,我们将把锁获取和跟踪逻辑放入另一个(内部)try finally
语句的finally
块中。原因是ThreadAbortException
不会中断最终执行
块。我们会有这样的东西:
var isTaken = false;
try
{
try
{
}
finally
{
await _semaphore.WaitAsync();
isTaken = true;
}
// todo
}
finally
{
if (isTaken)
{
_semaphore.Release();
}
}
不幸的是,我们仍然不安全。问题是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();
}
}
如果WaitAsync
失败,Release
是否仍会退出信号量一次?肯定会导致冗余调用。修复了我的答案async/await
不会改变async
方法中异常的行为。它只会改变异常的处理方式。因此,“同步字”的正确之处还在这里。本文解释了在执行<代码>锁(){{} /代码>时,同样的问题:如果我们确信我们的代码不调用线程,为什么我们会考虑处理TraceBrad异常?特别是在.NET Core中引入的更改方面,Thread.Abort
被基于CancellationToken的更安全的方法所取代。关于这一点,有一个很好的线索:对于.NET Framework项目,我想说,考虑到ThreadAbortException仍然有效,因为很难预见它可以被抛出的所有方式——包括第三方库和消费者。
await _semaphore.WaitAsync();
// ThreadAbortException happens here
try
{
// todo
}
finally
{
_semaphore.Release();
}
try
{
// ThreadAbortException happens here
await _semaphore.WaitAsync();
// todo
}
finally
{
_semaphore.Release();
}
var isTaken = false;
try
{
try
{
}
finally
{
await _semaphore.WaitAsync();
isTaken = true;
}
// todo
}
finally
{
if (isTaken)
{
_semaphore.Release();
}
}
var isTaken = false;
try
{
do
{
try
{
}
finally
{
isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
}
}
while(!isTaken);
// todo
}
finally
{
if (isTaken)
{
_semaphore.Release();
}
}