简体   繁体   中英

Wait for a task in multiple tasks at the same time

Imagine an initialization task and two workers:

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);
});

After 1 second, TaskB and TaskC are printing to the console and continuing as expected.

However, when the task which finishes first uses no asynchronous methods after awaiting Init, the other task await Init call does not finish:

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) { }
});

The latter example prints only "C finished waiting" to the console. Why is that so?

Behind the scenes, async/await builds a state machine based on task continuations. You can mimic it using ContinueWith and a bit of ingenuity (the details are beyond the scope of this answer).

What you need to know is that it uses continuations, and that continuations - by default - happen syncrhonously, one at a time.

In this case, both B and C are creating continuations on Init . These continuations will run after the other. Thus, when C continues first and goes into the infinite loop, it prevents B to continue.


The solution? Well, other than avoiding infinite loops, you could use a task with asynchronous continuations. A simple way to do it on top of Task.Run is the following:

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

Addendum

As per OP comment, the infinite loop represent a blocking call. We can solve that situation by a different approach:

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

This way, the blocking call is not int the continuation of Init (instead, it would be in a continuation of a continuation), and thus it won't prevent other continuations of Init to run.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM