C# - Dispatcher.InvokeAsync 未等待 ContinueWith

C# - Dispatcher.InvokeAsync ContinueWith not awaited

假设 ChartViewModels 是一个 ObservableCollection<T>,以下代码按预期工作:

await Dispatcher.InvokeAsync(() =>
{
    ChartViewModels.Clear();
    ChartViewModels.AddRange(initializedCharts);
}, DispatcherPriority.DataBind, mCancellationToken.Token);

await UpdateChartsWithValuesAsync(chartsToInitialize, ChartViewModels).ConfigureAwait(false);

相反,如果我将 UpdateChartsWithValuesAsync 方法调用包装在 ContinueWith 委托中,则不再等待该方法。我已经尝试将 ConfigureAwait(false) 更改为 true 但似乎没有任何改变。编辑后的代码下方:

await Dispatcher.InvokeAsync(() =>
{
    ChartViewModels.Clear();
    ChartViewModels.AddRange(initializedCharts);
}, DispatcherPriority.DataBind, mCancellationToken.Token).Task
.ContinueWith(async t => await UpdateChartsWithValuesAsync(chartsToInitialize, ChartViewModels).ConfigureAwait(false), mCancellationToken.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current).ConfigureAwait(false);

Dispatcher 中的代码始终在 ContinueWith 委托之前执行,但它不再等待 UpdateChartsWithValuesAsync 完成,从而引发严重错误。

谁能解释一下这种行为?谢谢

WPF、.NET Framework 4.7 项目

Dispatcher.InvokeAsyncreturns一个DispatcherOperation。这是一个可等待的类型,因为它实现了一个名为 GetAwaiter() 的方法,returns 一个实现了 INotifyCompletion.

的类型

当您直接在 Dispatcher.InvokeAsync 的结果上使用 await 时,在调用 UpdateChartsWithValuesAsync.

之前等待 DispatcherOperation 完成

在你的第二个例子中,你没有直接等待它;您正在等待链式表达式的结果:

Dispatcher
    .InvokeAsync()   // returns DispatcherOperation
    .Task            // returns Task
    .ContinueWith(); // returns another Task

因此只等待最终对象 (Task),这意味着您传递给 ContinueWith 的函数可能会在 Dispatcher.InvokeAsync 完成之前执行。

如果您正在使用异步/等待,谨慎的做法是在异步方法中使用关键字,因为与基于回调的操作混合使用会导致混淆代码,例如这个。

如其他答案所述,您不应将 async t => await UpdateChartsWithValuesAsync 放入 ContinueWith 回调中,因为它会导致等待 Task<Task> ContinueWith(...) 方法,该方法最终会立即完成。

如果你真的想在最外层等待 ContinueWith 完成 await 你应该 Unwrap() 你的 Task<Task> 并等待,或者在里面使用同步 API,但是记住同步上下文的正确管理。

await Dispatcher.InvokeAsync(() =>
{
    ChartViewModels.Clear();
    ChartViewModels.AddRange(initializedCharts);
}, DispatcherPriority.DataBind, mCancellationToken.Token).Task
.ContinueWith(t => UpdateChartsWithValuesAsync(), mCancellationToken.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current)
.Unwrap()
.ConfigureAwait(false);

请注意,await 被翻译成涉及 ContinueWith 的代码块,其中包含 ConfigureAwait 选项等,因此您最好像在第一次尝试时那样使用 async await 结构例如。

简单地说,.ContinueWith()在其实现中不做await,而是按原样运行传入的委托和returns一个任务的任务(Task<Task>> ).这个外部任务,因为没有等待传入的委托,所以立即完成。

我的建议是,在这种情况下不要使用 .ContinueWith(),只需坚持等待即可。如果你真的想保留当前代码,你可以做 .ContinueWith().Unwrap(),有什么用。

这里还有一个主题中的另一个相关问题:Use an async callback with Task.ContinueWith

如果你想深入了解,ContinueWith的源代码:https://referencesource.microsoft.com/#mscorlib/system/threading/tasks/Task.cs,4532