C# 同时在多个任务中等待一个任务

C# 同时在多个任务中等待一个任务,c#,multithreading,async-await,task,C#,Multithreading,Async Await,Task,设想一个初始化任务和两个工人: 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 ()

设想一个初始化任务和两个工人:

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

后一个示例仅将“C已完成等待”打印到控制台。为什么会这样?

在幕后,async/await基于任务连续性构建状态机。您可以使用
ContinueWith
和一点独创性来模拟它(细节超出了本答案的范围)

您需要知道的是,它使用continuations,默认情况下,continuations是同步发生的,一次一个

在本例中,B和C都在
Init
上创建continuation。这些延续将接踵而至。因此,当C首先继续并进入无限循环时,它会阻止B继续


解决方案是什么?好的,除了避免无限循环之外,您还可以使用具有异步延续的任务。在
任务上运行
的简单方法如下:

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
的其他延续运行。

在幕后,async/wait基于任务延续构建状态机。您可以使用
ContinueWith
和一点独创性来模拟它(细节超出了本答案的范围)

您需要知道的是,它使用continuations,默认情况下,continuations是同步发生的,一次一个

在本例中,B和C都在
Init
上创建continuation。这些延续将接踵而至。因此,当C首先继续并进入无限循环时,它会阻止B继续


解决方案是什么?好的,除了避免无限循环之外,您还可以使用具有异步延续的任务。在
任务上运行
的简单方法如下:

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
的其他延续运行。

不可复制:我将我的演示推到github:它在我的W10桌面上完全可复制。编辑:不完全可复制,当B首先完成时,它工作正常(显然),如果你使用2个whiletrue循环,它也会在ideone.com上复制:不可复制:我将我的演示推到github:它在我的W10桌面上完全可复制。编辑:不是完全可复制的,当B首先完成时它工作良好(显然),如果你使用2个whiletrue循环,它也会在ideone.com上复制:有趣!不幸的是,一个工作人员在init之后调用了一个阻塞库函数,但您继续使用这个提示确实很有帮助@本尼:我已经扩展了答案。如果没有用于阻塞呼叫的异步API,请考虑我在那里介绍的方法。谢谢!不过,它是否优于使用ContinueWith?@Benni并行运行ContinueWith需要更多的注意,以确保同时运行的两个ContinueWith不会互相踩到对方的脚趾(引入比赛条件),如果你不愿意说这不会发生,我建议不要使用它。在这方面,以同步方式运行它们更安全,因此,它是默认的。。。事实上,这曾经是唯一的选择
RunContinuationsAsSynchronously
将让您利用并行性—这可能会或可能不会产生更好的性能,请记住,这通常意味着使用线程池中的更多线程。有趣!不幸的是,一个工作人员在init之后调用了一个阻塞库函数,但您继续使用这个提示确实很有帮助@本尼:我已经扩展了答案。如果没有用于阻塞呼叫的异步API,请考虑我在那里介绍的方法。谢谢!不过,它是否优于使用ContinueWith?@Benni并行运行ContinueWith需要更多的注意,以确保同时运行的两个ContinueWith不会互相踩到对方的脚趾(引入比赛条件),如果你不愿意说这不会发生,我建议不要使用它。在这方面,以同步方式运行它们更安全,因此,它是默认的。。。事实上,这曾经是唯一的选择
RunContinuationsAsynchronously
将让您利用并行性,这可能会产生更好的性能,也可能不会,请记住,这通常意味着使用线程池中的更多线程。