Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/276.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# try块之前/之后的信号量lim.WaitAsync_C#_.net_Locking_Async Await_Semaphore - Fatal编程技术网

C# try块之前/之后的信号量lim.WaitAsync

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

我知道在同步世界中,第一个代码片段是正确的,但是WaitAsync和async/await magic是什么呢?请给我一些.net内部组件

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();
        }
    }