Await /async和跳出框框

本文关键字:async Await | 更新日期: 2023-09-27 18:05:03

我有一个关于await/async和在与预期略有不同的场景中使用async方法的问题,例如不直接等待它们。例如,假设我有两个例程需要并行完成,其中两个例程都是异步方法(它们内部有等待)。我正在使用await TAsk.WhenAll(...),它反过来期望等待某种任务列表。我所做的是这样的:

            await Task.WhenAll(new Task[]
            { 
                Task.Run(async () => await internalLoadAllEmailTargets()),
                Task.Run(async () => await internalEnumerateInvoices())
            });

这对我来说似乎过于复杂,因为我正在创建异步任务,其唯一目的是调用另一个任务。我不能只是使用从异步方法状态引擎返回的任务吗?然而,我没有做到这一点,因为编译器将每个直接提到的异步方法都视为调用点:

            // this doesn't seem to work ok
            await Task.WhenAll(new Task[]
            { 
                internalLoadAllEmailTargets(),
                internalEnumerateInvoices()
            }); 

如果是这样,它似乎一个接一个地同步调用,如果我在方法前面放置await,它就不再是Task了。在普通等待之外,是否有一些关于如何处理异步方法的规则手册?

Await /async和跳出框框

每个async方法开始同步执行,但是当它到达第一个await时,它可能会异步执行。所以这一行:

await Task.WhenAll(internalLoadAllEmailTargetsAsync(), internalEnumerateInvoicesAsync());

应该可以正常工作。它大致相当于:

var _1 = internalLoadAllEmailTargetsAsync();
var _2 = internalEnumerateInvoicesAsync();
await Task.WhenAll(_1, _2);

如果你的方法是真正异步的,那么这应该没问题。

现在,如果你的方法实际上在做一些同步工作——比如说,繁重的cpu绑定代码——那么你可能想用Task.Run来调用它们(如果你的调用代码在一个UI线程上)。

您有一些代码,创建Task对象,它将像往常一样被调用,即同步。控件只有在创建Task之后才会返回给调用代码,而在创建async的情况下,它将在第一个await之后返回。因此,如果您的方法的某些部分将以阻塞方式调用,这是一个问题,您可以在开始时使用Task.Yield,只是要小心SynchronizationContext和线程切换。

但是在大多数情况下,这种情况并没有什么问题,因为创建Task的代码既小又快,而实际的计时是由某种IO操作引起的。