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.InvokeAsync
returns一个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
假设 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.InvokeAsync
returns一个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