Luk*_*oid 13 c# semaphore base-class-library async-await .net-4.5
在我们的一个课程中,我们大量使用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?");
}
}
}
Run Code Online (Sandbox Code Playgroud)
而这里的LINQPad实现的链接.
运行上一个示例几次,有时您会看到取消WaitAsync
不再允许任何线程进入.
更新
看来这在每台机器上都不可重复,如果你设法重现问题,请留下评论说.
我已设法重现以下问题:
我无法重现以下问题:
更新2
我在这里向微软提交了一个错误,但到目前为止它们无法重现,所以如果尽可能多的尝试并运行示例项目它会真正有用,它可以在链接问题的附件选项卡上找到.
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
}
}
Run Code Online (Sandbox Code Playgroud)
与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);
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
在 4.5.1 中,RemoveAsyncWaiter 将返回 false,而 WaitAsync 将返回 true。
归档时间: |
|
查看次数: |
3980 次 |
最近记录: |