通过Dispatcher调用async lambda时使用Double await

本文关键字:Double await lambda Dispatcher 调用 async 通过 | 更新日期: 2023-09-27 18:12:35

private async Task<T> LoadForm(WebControlAsync browser, Uri url)
{ ... }
var forms = await await _dispatcher.InvokeAsync(async () => await LoadForm(browser, form.Url));

我不明白为什么我必须在这里使用两个await来获得forms中的T ?所以看起来InvokeAsync返回Task<Task<T>>。但是当我像这样调用同步方法:

var forms = await _dispatcher.InvokeAsync(() => FillForm(forms, url));

只需要一个await。所以原因似乎是async lambda。我明白如果我这样写:

var forms = await await _dispatcher.InvokeAsync(() => LoadForm(browser, form.Url));

LoadForm的返回类型是Task<T>, InvokeAsync返回Task<lambda return type>,因此它确实是Task<Task<T>>。但是,当Task方法是await 'ed时,它不是从Task"解开"实际返回类型吗?所以如果我写:

var forms = await LoadForm(browser, form.Url);

forms应该是T,而不是Task<T>。为什么同样的事情不会发生在async lambda中?

通过Dispatcher调用async lambda时使用Double await

你已经回答了自己的问题。InvokeAsync返回一个Task<T>,其中T是提供给它的Func<TResult>委托的返回类型。当你使用异步lambda时,你不再处理Func<TResult>,而是Func<Task<TResult>>

您认为应该自动展开的原因可能是由于过去使用了Task.Run。然而,应该注意的是,Task.Run有一个接受Func<T>并返回Task<T>的重载,和一个接受Func<Task<T>>的重载,而仍然返回Task<T>,所以你认为这个在掩护后面为你发生的打开是理所当然的。然而,这并不适用于你的情况。

Dispatcher.InvokeAsync在这方面与Task.Factory.StartNew相似。它没有专门处理Func<Task<TResult>>的重载,所以你只能"手动"打开包装。

问题是,考虑在您的场景中是否真的需要InvokeAsync。在dispatcher线程被工作过载的场景之外,它不会提供太多信息。它返回的Task通常会立即完成(不需要等待由async lambda创建的Task),只是给您多一层包装来处理。LoadForm已经是async了,所以你不妨只使用Dispatcher.Invoke,它会在正确的SynchronizationContext上触发你的async lambda,并最终返回你想要等待的任务,或者,如果你知道你的LoadForm将始终被正确的SynchronizationContext调用(即,在UI线程上),完全省略dispatcher调用,只是让async/await做它的事情。