我正在摆弄
ContinueWith
函数,但最终我不明白它。
在此示例代码中:
var s = Task.FromResult(true).ContinueWith(async t => t).ContinueWith(async t => t);
await await await await await s;
每个“等待”在等待什么?
你正在做一些奇怪的事情,并且你得到了奇怪的输出。
让我们简单一点:
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>>
。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
)。
我们先用很幼稚的方式回答一下:
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 会很自然地为你做,没有麻烦。
只是为了进一步扩展/说明 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;