通过Dispatcher调用异步lambda时双重等待

Ale*_*bin 3 c# wpf dispatcher task-parallel-library async-await

private async Task<T> LoadForm(WebControlAsync browser, Uri url)
{ ... }

var forms = await await _dispatcher.InvokeAsync(async () => await LoadForm(browser, form.Url));
Run Code Online (Sandbox Code Playgroud)

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

var forms = await _dispatcher.InvokeAsync(() => FillForm(forms, url));
Run Code Online (Sandbox Code Playgroud)

它只需要单身await.所以原因似乎是异步lambda.我明白,如果我这样写:

var forms = await await _dispatcher.InvokeAsync(() => LoadForm(browser, form.Url));
Run Code Online (Sandbox Code Playgroud)

然后返回类型LoadForm是Task<T>和InvokeAsync返回Task<lambda return type>所以它确实会Task<Task<T>>.但是当一个Task方法被await编辑时,是不是"从"开始实际返回类型Task?所以,如果我写:

var forms = await LoadForm(browser, form.Url);
Run Code Online (Sandbox Code Playgroud)

forms会T,不是Task<T>.为什么在异步lambda中不会发生同样的情况?

Kir*_*kiy 8

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

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

Dispatcher.InvokeAsyncTask.Factory.StartNew在这方面类似.它没有专门处理的过载Func<Task<TResult>>,所以你不得不"手动"展开.

事情是,考虑一下InvokeAsync你的场景中是否真的需要.除了调度程序线程超负荷工作之外,它不会给你太多.在Task其返回通常会立即完成(不等待Task你创建async拉姆达),只是给你包装处理的又一个层.LoadForm已经是async这样你可能只是使用Dispatcher.Invoke哪个会async在正确的情况下触发你的lambda SynchronizationContext并最终返回你想要等待的任务,或者,如果你知道你的LoadForm总是会被正确调用SynchronizationContext(也就是说,在UI上)线程),完全省略调度程序调用,只是让它async/await做的事情.

  • @AlekseyShubin,你所做的并不是真正意义上的错误.它只会导致一些非常规的语法,更难以理解.当你绝对不能等待调度程序线程通过`Invoke`开启你的`FillForm`任务时(这本身就是一个阻塞操作,如果你的调度员忙,可能需要时间),使用`Dispatcher.InvokeAsync`是一个有效的选择,即使它与`async`(或简称`Task`-returning)委托结合使用时强制你使用双`await`或`Unwrap`确实会导致有趣的语法. (2认同)