gpt4 book ai didi

c# - 取消SemaphoreSlim.WaitAsync保持信号量锁

转载 作者:可可西里 更新时间:2023-11-01 08:29:33 26 4
gpt4 key购买 nike

在我们的一个类(class)中,我们大量使用 SemaphoreSlim.WaitAsync(CancellationToken)并取消它。

当对 WaitAsync 的挂起调用在调用 SemaphoreSlim.Release() 后不久被取消时,我似乎遇到了问题(很快,我的意思是在 ThreadPool 有机会处理排队的项目之前),它将信号量置于无法获取进一步锁定的状态。

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

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

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的取消不再允许任何线程进入。

更新

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

我已经成功重现了以下问题:

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

我无法重现以下问题:

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

更新 2

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

最佳答案

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。

关于c# - 取消SemaphoreSlim.WaitAsync保持信号量锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21019895/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com