Ожидание задачи в нескольких задачах одновременно - PullRequest
0 голосов
/ 06 июля 2018

Представьте себе задачу инициализации и двух рабочих:

var Init = Task.Run(async () =>
{
    await Task.Delay(1000);
});
var TaskB = Task.Run(async () =>
{
    await Init;
    Console.WriteLine("B finished waiting");
    await Task.Delay(10000000);
});
var TaskC = Task.Run(async () =>
{
    await Init;
    Console.WriteLine("C finished waiting");
    await Task.Delay(10000000);
});

Через 1 секунду TaskB и TaskC печатают на консоль и продолжают работу, как и ожидалось.

Однако, когда задача, которая завершается первой, не использует асинхронные методы после ожидания Init, вызов другой задачи await Init не завершается:

var Init = Task.Run(async () =>
{
    await Task.Delay(1000);
});
var TaskB = Task.Run(async () =>
{
    await Init;
    Console.WriteLine("B finished waiting");
    await Task.Delay(5000);
});
var TaskC = Task.Run(async () =>
{
    await Init;
    Console.WriteLine("C finished waiting");
    while (true) { }
});

В последнем примере на консоль выводится только «С завершил ожидание». Почему это так?

1 Ответ

0 голосов
/ 06 июля 2018

За кулисами async / await создает конечный автомат на основе продолжений задач. Вы можете имитировать это, используя ContinueWith и немного изобретательности (детали выходят за рамки этого ответа).

Что вам нужно знать, так это то, что он использует продолжения, и что продолжения - по умолчанию - происходят синхронно, по одному за раз.

В этом случае и B, и C создают продолжения на Init. Эти продолжения будут идти за другим. Таким образом, когда C продолжается первым и входит в бесконечный цикл, он препятствует продолжению B.


Решение? Ну, кроме избежания бесконечных циклов, вы можете использовать задачу с асинхронными продолжениями. Простой способ сделать это поверх Task.Run заключается в следующем:

var Init = Task.Run(async () =>
{
    await Task.Delay(1000);
}).ContinueWith(_ => { }, TaskContinuationOptions.RunContinuationsAsynchronously);

Добавление

Согласно комментарию OP, бесконечный цикл представляет блокирующий вызов. Мы можем решить эту ситуацию другим подходом:

var TaskC = Task.Run(async () =>
{
    await Init;
    Console.WriteLine("C finished waiting");
    await Task.Run(() => {while (true) { }});
});

Таким образом, блокирующий вызов не является продолжением Init (вместо этого он будет продолжением продолжения), и, таким образом, он не будет препятствовать выполнению других продолжений Init.

...