首页 > 解决方案 > 正在使用 Task.Run 进行异步等待吗?

问题描述

Task.Run(fn1sync());
Task.Run(fn2sync());

上面的代码在新线程中启动每个任务,并在将控制权返回给主线程的同时并行运行它们。

除了上面,我可以更改函数以使用异步等待的概念并编写:

var v1 = fn1Async()
var v2 = fn2Async()
...do some work..
Task.WaitAll(v1, v2)

现在 .NET 将自动处理是使用单线程还是多线程,并且并行运行它们并立即将控制权传递给主线程。

我什么时候需要使用这些方法中的任何一种?

标签: c#async-awaittask-parallel-library

解决方案


我不确定您的问题的标题是否完全符合您在正文中提出的实际问题,所以我将使用正文:

您的两个示例之间至少有一个非常大的区别。有了这个(选项1):

Task.Run(fn1sync());
Task.Run(fn2sync());

您无法等待这两项任务完成。它们将并行运行(可能 - 但不能保证 - 在不同的线程中)。它们都将立即开始并在完成时结束。之后的任何代码也将立即执行,而无需等待任务完成。

有了这个(选项2):

var v1 = fn1Async()
var v2 = fn2Async()
...do some work..
Task.WaitAll(v1, v2)

你坚持任务,然后在“做一些工作”之后,你明确地等待两者都完成后再继续。最后一行之后的任何内容都不会执行,直到两者都完成。

这个问题变得更有趣了——也许这就是你想要的?- 如果你以这种方式重写选项 1(我们称之为选项 3):

var v1 = Task.Run(fn1sync());
var v2 = Task.Run(fn2sync());
...do some work...
Task.WaitAll(v1, v2);

选项 2 和选项 3 是否相同?答案仍然是否定的。

当您调用异步方法/委托/等时。不等待它,这是选项 2 所做的,委托是否在调用分支可以继续之前实际完成取决于委托是否调用任何真正的异步代码。例如,您可以编写此方法:

public Task fn1sync()
{
    Console.Write("Task complete!");
    return Task.CompletedTask;
}

Console.Write行将始终在“做一些工作”之前执行。为什么?因为如果您Task从字面上考虑返回对象,那么根据定义,在它写入控制台之前fn1sync不能将 a 返回给您的调用代码(这是一个同步操作)。Task将“Console.Write”替换为非常慢甚至阻塞的东西,你就失去了异步的好处。

Task.Run另一方面,当您使用时,您保证控制流将立即返回到您的下一行代码,无论委托执行多长时间,也不管委托是否实际包含任何异步代码。因此,您可以确保您的调用线程不会因委托中的任何内容而减慢速度。事实上,Task.Run当您必须调用您有理由相信会很慢的同步代码时使用它是一个好主意 - 遗留 I/O、数据库、本机代码等。

调用async方法而不等待它或将其包装在Task.Run(选项 2)中的用例可能不太明显,但它们确实存在。只要您知道您正在调用的代码是表现良好的异步代码(即它不会阻塞您的线程),这是一种“即发即弃”的方法。例如,如果fn1Async在选项 2 中看起来像这样:

public Task fn1sync()
{
    return Task.Run(()=>
    {
         Console.Write("Task complete!");
    });
}

那么选项 2 和 3 将完全等效且同样有效。

更新:回答你的问题,“你能告诉我为什么主线程中的 await 会导致它阻塞等待结果,而函数内的 await 会导致控制权传递给主线程吗?”

我同意您可能希望找到关于 async/await/Task 的更全面的入门的一些评论。但话虽如此,这都是关于分支以及await关键字在编译时如何解释的。

当你写:

async Task MyMethodAsync()
{
    Console.Write("A");
    int result = await AnotherMethodAsync();
    Console.Write("B");
    return;
}

这基本上被转换为:

Task MyMethodAsync()
{
    Console.Write("A");
    Task<int> task1 = AnotherMethodAsync();  // *should* return immediately if well behaved
    Task task2 = task1.ContinueWith((Task<int> t) => 
    {
        // t is same as task1 (unsure if they're identical instances but definitely equivalent)
        int result = t.Value;
        Console.Write("B");     ​
        ​return;
   ​ }); // returns immediately
    return task2; 
}

方法中第一个ed 语句之前的所有内容都会await同步执行 - 在调用者的线程上。但是第一个等待的语句之后的所有内容都成为一个回调,只有在第一个等待的语句 - task1- 完成后才会执行。

重要的是,ContinueWith返回自己的Task-task2这就是返回给调用者的内容MyMethodAsync,允许await他们选择或不选择。该行"Console.Write("B")保证在task1完成之前不会被执行。但是,相对于调用后的代码何时执行它MyMethodAsync 完全取决于该调用是否被await编辑。

因此:

async Task Main()
{
     await MyMethodAsync();
     Console.Write("C");
}

变成:

Task Main()
{
     return MyMethodAsync().ContinueWith(t => 
     {
          // Doesn't get executed until MyMethodAsync is done
          Console.Write("C");
     });
}

因此保证输出为:ABC

然而:

void Main()
{
     Task t = MyMethodAsync(); // Returns after "A" is written

     // Gets executed as soon as MyMethodAsync
     // branches to `await`ed code, returning the `Task`
     // May or may not be before "B"
     Console.Write("C");
}

按字面意思执行t,在“A”之后但在“C”之前以不完整的状态返回。因此可以保证输出以“A”开头,但是如果不知道在做什么,接下来是“B”还是“C”将是不可预测AnotherMethodAsync的。


推荐阅读