理解多个连续的await语句

问题描述 投票:0回答:3

我正在摆弄

ContinueWith
函数,但最终我不明白它。

在此示例代码中:

var s = Task.FromResult(true).ContinueWith(async t => t).ContinueWith(async t => t);
await await await await await s;

每个“等待”在等待什么?

c# asynchronous async-await task-parallel-library
3个回答
3
投票

你正在做一些奇怪的事情,并且你得到了奇怪的输出。

让我们简单一点:

var s = Task.FromResult(true).ContinueWith(t => t);
await await s;
  • Task.FromResult(true)
    返回
    Task<bool>
  • ContinueWith
    完成时传递
    Task<bool>
    :想法是它可以查询任务以查看它是否成功/失败,并获取其结果。
  • 总体思路是
    ContinueWith
    返回一个值,而不是
    Task
    。但是,您将从它返回
    t
    ,即
    Task<bool>
    ContinueWith
    将该返回值包装在
    Task
    中,其想法是,一旦
    Task
    完成并且延续已运行,
    Task.FromResult(true)
    就会完成。这会给你一个
    Task<Task<bool>>

这并不that不寻常,这就是

Task.Unwrap()
存在的原因。

当您使延续采用

async t => t
时,您就是在告诉编译器生成一个返回
Task<T>
的委托。在本例中,该方法采用
Task<bool>
并返回
Task<Task<bool>>

请注意,这并不常见。你通常不会写这样的东西:

async Task<Task<bool>> Foo()
{
    await ...;
    return Task.FromResult(true);
}

如果您有异步任务返回方法,则只需返回一个值,而不是另一个任务。

然后延续将其包装在另一个任务中以获得

Task<Task<Task<bool>>>

然后你在第二个延续中执行相同的操作,并获得更多任务。

您可以将其全部梳理清楚并弄清楚每个任务代表什么,但这并没有多大意义:您正在做一些愚蠢的事情,并且得到了愚蠢的结果。

请记住,

ContinueWith
早于异步/等待:你实际上不应该将两者混合使用(事实上,这些天你真的不应该使用
ContinueWith
)。


2
投票

我们先用很幼稚的方式回答一下:

var s = Task.FromResult(true).ContinueWith(async t => t ).ContinueWith(async t => t);
await await await await await s;

首先,我们将表达式拆分为其组成部分:

var s1 = Task.FromResult(true);
var s2 = s1.ContinueWith(async t => t );
var s3 = s2.ContinueWith(async t => t);

如果我们用完整类型扩展前两个

var
声明,我们会得到:

Task<bool> s1 = Task.FromResult(true);
Task<Task<Task<bool>>> s2 = s1.ContinueWith(async t => t );

第一个很简单,它是一个会产生布尔值的任务。接下来需要一些解释。

当您执行

task.ContinueWith(...)
时,您会出现一个重载,该重载会接收
...
部分之前的任务,在本例中为
task
参考。该任务被传递到延续方法中,因此:

s1.ContinueWith(async t => t)

表示

s1
将作为
async t => t
传递到
t
延续,然后返回。

此外,当您这样做时:

var x = task.ContinueWith(...)

你得到一个可以等待的任务,从

...
获取返回值。在这种情况下,这是另一个任务,因为您刚刚返回了传入的任务。

所以这意味着这个表达式:

var s2 = s1.ContinueWith(async t => t );

实际上会从ContinueWith返回一个任务,并且该任务包装来自

t => t
的另一个任务。所以你可以从ContinueWith中等待任务来获取
s1
任务,该任务也可以等待。

让我们充分展开表达式:

Task<bool> s1 = Task.FromResult(true);
Task<Task<Task<bool>>> s2 = s1.ContinueWith(async t => t );
Task<Task<Task<Task<Task<bool>>>>> s = s2.ContinueWith(async t => t);

每个级别都会添加另一个“包装另一个任务的任务,该任务包装......”。

当我们解开 5 级等待时,我们得到:

  • 首先我们等待决赛
    ContinueWith
  • 这会产生一个任务,来自
    async t = t
    ,所以我们等待这个
  • 这会产生一个任务,第一个
    ContinueWith
    ,所以我们等待这个
  • 这会产生一个任务,第一个
    async t = t
    ,所以我们等待这个
  • 这会产生一个任务,原来的
    Task.FromResult(true)

所以最终的代码可以这样看:

var s = Task.FromResult(true).ContinueWith(async t => t ).ContinueWith(async t => t);
                    ^                ^         ^                ^             ^
                    |                |         |                |             |
                await       await  await       |    await    await s;         |
                                |              |      |                       |
                                +--------------+      +-----------------------+

请注意,所有这些都是同步发生的,没有等待,这里根本没有异步代码。这是因为所有涉及的任务都会立即内联完成,并产生结果。

总的来说,ContinueWith 最好留给框架和库作者,而不是我们打算再使用的真正结构。有很多事情你需要注意,特别是在异常处理方面,async/await 会很自然地为你做,没有麻烦。


2
投票

只是为了进一步扩展/说明 canton7 的观点

如果您一次执行一项操作(而不是链接它们),那么应该更容易理解为什么需要 5 个

await

Task<bool> t1 = Task.FromResult(true);
Task<Task<Task<bool>>> t2 = t1.ContinueWith(async t => t);
Task<Task<Task<Task<Task<bool>>>>> t3 = t2.ContinueWith(async t => t);
    
Task<Task<Task<Task<bool>>>> r1 = await t3;
Task<Task<Task<bool>>> r2 = await r1;
Task<Task<bool>> r3 = await r2;
Task<bool> r4 = await r3;
bool r = await r4; 

通过对

UnWrap
进行
ContinueWith
调用,所需
await
的数量为 3 个

Task<bool> t1 = Task.FromResult(true);
Task<Task<bool>> t2 = t1.ContinueWith(async t => t).Unwrap();
Task<Task<Task<bool>>> t3 = t2.ContinueWith(async t => t).Unwrap();
    
Task<Task<bool>> r1 = await t3;
Task<bool> r2 = await r1;
bool r = await r2;
© www.soinside.com 2019 - 2024. All rights reserved.