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. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |