【问题标题】:Cancellation of SemaphoreSlim.WaitAsync keeping semaphore lock取消 SemaphoreSlim.WaitAsync 保持信号量锁
【发布时间】:2014-01-27 23:37:52
【问题描述】:

在我们的一堂课中,我们大量使用SemaphoreSlim.WaitAsync(CancellationToken) 并取消了它。

当对WaitAsync 的呼叫在呼叫SemaphoreSlim.Release() 后不久被取消时,我似乎遇到了问题(我的意思是在ThreadPool 有机会处理排队项目之前),它将信号量置于无法获取更多锁的状态。

由于在调用Release()Cancel() 之间是否执行ThreadPool 项的不确定性,以下示例并不总是说明问题,对于这些情况,我已明确表示忽略运行。

这是我试图证明问题的示例:

void Main()
{
    for(var i = 0; i < 100000; ++i)
        Task.Run(new Func<Task>(SemaphoreSlimWaitAsyncCancellationBug)).Wait();
}

private static async Task SemaphoreSlimWaitAsyncCancellationBug()
{
    // Only allow one thread at a time
    using (var semaphore = new SemaphoreSlim(1, 1))
    {
        // Block any waits
        semaphore.Wait();

        using(var cts1 = new CancellationTokenSource())
        {
            var wait2 = semaphore.WaitAsync(cts1.Token);
            Debug.Assert(!wait2.IsCompleted, "Should be blocked by the existing wait");

            // Release the existing wait
            // After this point, wait2 may get completed or it may not (depending upon the execution of a ThreadPool item)
            semaphore.Release();         

            // If wait2 was not completed, it should now be cancelled
            cts1.Cancel();             

            if(wait2.Status == TaskStatus.RanToCompletion)
            {
                // Ignore this run; the lock was acquired before cancellation
                return;
            }

            var wasCanceled = false;
            try
            {
                await wait2.ConfigureAwait(false);

                // Ignore this run; this should only be hit if the wait lock was acquired
                return;
            }
            catch(OperationCanceledException)
            {
                wasCanceled = true;
            }

            Debug.Assert(wasCanceled, "Should have been canceled");            
            Debug.Assert(semaphore.CurrentCount > 0, "The first wait was released, and the second was canceled so why can no threads enter?");
        }
    }
}

还有here LINQPad 实现的链接。

运行之前的示例几次,有时您会看到取消WaitAsync 不再允许任何线程进入。

更新

看来这不是在每台机器上都可以重现的,如果您设法重现该问题,请发表评论说。

我已经设法在以下方面重现了该问题:

  • 3x 64 位 Windows 7 机器运行 i7-2600
  • 运行 i7-3630QM 的 64 位 Windows 8 机器

我无法重现以下问题:

  • 运行 i5-2500k 的 64 位 Windows 8 机器

更新 2

我已经向 Microsoft here 提交了一个错误,但是到目前为止他们无法重现,所以如果尽可能多的人可以尝试运行示例项目,这将非常有帮助,它可以在附件选项卡上找到链接的问题。

【问题讨论】:

  • 你在哪个框架上运行? .NET 4.5?单声道?
  • 我应该提到的是,.NET 4.5 添加了一个标签,以防万一这是 .NET 框架的问题。
  • 你为什么用 BCL 标记它?
  • 因为我怀疑这可能是 BCL 的一部分 SemaphoreSlim 中的错误。
  • 感谢@Noseratio 的链接,我已将此报告为错误,here is the link

标签: c# .net-4.5 async-await semaphore base-class-library


【解决方案1】:

SemaphoreSlim 在 .NET 4.5.1 中已更改

.NET 4.5 版本的 WaitUntilCountOrTimeoutAsync 方法是:

private async Task<bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
{ 
    [...]

    // If the await completed synchronously, we still hold the lock.  If it didn't, 
    // we no longer hold the lock.  As such, acquire it. 
    lock (m_lockObj)
    { 
        RemoveAsyncWaiter(asyncWaiter);
        if (asyncWaiter.IsCompleted)
        {
            Contract.Assert(asyncWaiter.Status == TaskStatus.RanToCompletion && asyncWaiter.Result, 
                "Expected waiter to complete successfully");
            return true; // successfully acquired 
        } 
        cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred
        return false; // timeout occurred 
    }
}

4.5.1 中的相同方法:

private async Task<bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
{
    [...]

    lock (m_lockObj)
    {
        if (RemoveAsyncWaiter(asyncWaiter))
        {
            cancellationToken.ThrowIfCancellationRequested(); 
            return false; 
        }
    }

    return await asyncWaiter.ConfigureAwait(false);
}

asyncWaiter 基本上是一个始终返回 true 的任务(在单独的线程中完成,始终返回 True 结果)。

Release 方法调用 RemoveAsyncWaiter 并安排 worker 以 true 完成。

这是 4.5 中可能存在的问题:

    RemoveAsyncWaiter(asyncWaiter);
    if (asyncWaiter.IsCompleted)
    {
        Contract.Assert(asyncWaiter.Status == TaskStatus.RanToCompletion && asyncWaiter.Result, 
            "Expected waiter to complete successfully");
        return true; // successfully acquired 
    } 
    //! another thread calls Release
    //! asyncWaiter completes with true, Wait should return true
    //! CurrentCount will be 0

    cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred, 
    //! throws OperationCanceledException
    //! wasCanceled will be true

    return false; // timeout occurred 

在 4.5.1 中 RemoveAsyncWaiter 将返回 false,而 WaitAsync 将返回 true。

【讨论】:

  • 您是说 .NET 4.5.1 的更新应该可以解决问题吗?
  • 是的,您应该尝试更新。顺便说一句,您在 MS Connect 问题中将 .NET Framework 4.5.1 指定为平台,因此他们可能没有尝试在 4.5 上运行您的代码。
  • 这真的很有帮助,当我更新到 Windows 8.1 时,我的家用 PC 上一定有 .NET 4.5.1,因此我无法重现。将所有内容更新到 4.5.1
  • 你从哪里得到这个源代码? referencesource.microsoft.com/netframework.aspx 似乎没有提供相同的代码,即使在下载 4.5update1 时也是如此。我之前在那里查看了两个版本之间是否进行了任何更改。
  • 4.5update1 与 4.5.1 RTM 不匹配(至少在 Windows 8.1 上)。使用反射器获取最新源。对不起 cmets 清理工作。
猜你喜欢
  • 2016-11-10
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 2021-03-20
  • 1970-01-01
  • 1970-01-01
  • 2010-09-16
  • 1970-01-01
相关资源
最近更新 更多