См. Ниже для полного минимального резо (консольный проект Net9).
Когда он тупик, и я прикрепляю отладчик, я вижу, что единственная входная задача к whenalltas Строка 50, пытаясь ввести блокировку.
Обратите внимание, что строка 50 ожидает входа в блокировку, была запущена как явно отдельная задача с помощью задачи.run, и, следовательно, не часть BatchProcessingTask , эта поток A в строке 33 ждет. Ожидается.using System.Diagnostics;
while (true)
{
var testTask = Test();
var time = Stopwatch.GetTimestamp();
await Task.WhenAny(testTask, Task.Delay(TimeSpan.FromSeconds(3)));
Console.WriteLine($"Test {Stopwatch.GetElapsedTime(time)}");
if (testTask.IsCompleted is false)
{
Debugger.Launch();
// only here so it won't start next iteration while the debugger attaches and breaks
await Task.Delay(TimeSpan.FromSeconds(60));
}
}
async Task Test()
{
var applicationStoppingCts = new CancellationTokenSource();
var batchProcessingTask = Task.CompletedTask;
var batchLock = new Lock();
applicationStoppingCts.Token.Register(() =>
{
lock (batchLock)
{
// this works as expected, nice and deadlock free
//batchProcessingTask.Wait();
// this deadlocks
// debugging we you can observe that batchProcessingTask.IsCompleted is true,
// but whenAllTask.IsCompleted is false, holding the lock hostage forever
var whenAllTask = Task.WhenAll([batchProcessingTask]);
whenAllTask.Wait();
}
});
batchProcessingTask = ProcessBatch();
await Task.Delay(TimeSpan.FromSeconds(1));
await applicationStoppingCts.CancelAsync();
return;
Task ProcessBatch()
{
var batchTask = Task.Delay(TimeSpan.FromSeconds(2));
Task.Run(async () =>
{
// omitted actual logic for checking new batch etc.
await batchProcessingTask;
lock (batchLock)
{
if(applicationStoppingCts.Token.IsCancellationRequested)
return;
batchProcessingTask = ProcessBatch();
}
});
return batchTask;
}
}
Подробнее здесь: https://stackoverflow.com/questions/796 ... mpleted-if