В одном из наших классов мы активно используем 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?");
}
}
}
И здесь ссылку на реализацию LINQPad.
Запустите предыдущий образец несколько раз, и иногда вы увидите, что отмена WaitAsync
больше не позволяет вводить нити.
Обновление
Похоже, что это не воспроизводится на каждой машине, если вам удастся воспроизвести проблему, пожалуйста, оставьте комментарий, сказав это.
Мне удалось воспроизвести проблему по следующему:
- 3x 64-разрядные машины под управлением Windows 7 с i7-2600
- 64-разрядная машина Windows 8 с i7-3630QM
Я не смог воспроизвести проблему следующим образом:
- 64-разрядная машина Windows 8 с i5-2500k
Обновление 2
Я зарегистрировал ошибку с Microsoft здесь, однако до сих пор они не могут воспроизвести, поэтому было бы действительно полезно, если возможно может попробовать и запустить образец проекта, его можно найти на вкладке вложений связанной проблемы.