c# – 取消SemaphoreSlim.Wait同步保持信号锁
|
在我们的一个课程中,我们大量使用
SemaphoreSlim.WaitAsync(CancellationToken)并取消它.
在调用 由于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的取消不再允许任何线程进入. 更新 看来这是不可重现的每台机器,如果你设法重现的问题,请留下这样的评论. 我已经设法重现了以下问题: >运行i7-2600的3x 64位Windows 7机器 我以前无法重现这个问题: > 64位Windows 8机器运行i5-2500k 更新2 我已经提交了Microsoft here的错误,但到目前为止,他们无法重现,所以如果尽可能多的尝试和运行示例项目,它可能会有帮助,它可以在链接问题的附件选项卡上找到. 解决方法
.NET 4.5.1中更改了SemaphoreSlim
.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,CancellationToken cancellationToken)
{
[...]
lock (m_lockObj)
{
if (RemoveAsyncWaiter(asyncWaiter))
{
cancellationToken.ThrowIfCancellationRequested();
return false;
}
}
return await asyncWaiter.ConfigureAwait(false);
}
asyncWaiter基本上是一个总是返回true的任务(在单独的线程中完成,总是使用True结果). 释放方法调用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. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
