C#-Dispatcher.InvokeAsync ContinueWith not waited

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

假设ChartViewModelsObservableCollection<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项目

c# .net wpf multithreading dispatcher
3个回答
1
投票

简单地说,.ContinueWith()在其实现中不执行await,而是按原样运行传入的委托并返回任务任务(Task<Task>>)。由于不等待传入的委托,所以此外部任务立即完成。

我的建议,在这种情况下,请勿使用.ContinueWith(),只需坚持等待即可。如果您真的想保留当前代码,则可以执行.ContinueWith().Unwrap(),这将解决问题。

这也是该主题中的另一个相关问题:Use an async callback with Task.ContinueWith

如果您想进一步了解,请参阅ContinueWith的源代码:https://referencesource.microsoft.com/#mscorlib/system/threading/tasks/Task.cs,4532


1
投票

Dispatcher.InvokeAsync返回DispatcherOperation。这是一个等待的类型,因为它实现了一个称为GetAwaiter()的方法,该方法返回实现了INotifyCompletion的类型。

当直接在await的结果上使用Dispatcher.InvokeAsync时,在调用DispatcherOperation之前要等待UpdateChartsWithValuesAsync的完成。

在第二个示例中,您不直接等待;您正在等待链接表达式的结果:

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

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

如果使用的是异步/等待,则在异步方法中使用关键字是谨慎的做法,因为与基于回调的操作混合会导致诸如此类的代码混乱。


1
投票

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

如果您真的想在大多数外部await中等待ContinueWith完成,则应该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构造。

© www.soinside.com 2019 - 2024. All rights reserved.