1

Assuming ChartViewModels is an ObservableCollection<T>, the following code works as expected:

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

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

Instead, if I wrap the the UpdateChartsWithValuesAsync method call in the ContinueWith delegate, the method is not awaited anymore. I already tried to change the ConfigureAwait(false) to true but nothing seems to change. Below the edited code:

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);

The code into the Dispatcher is always executed before the ContinueWith delegate, but it doesn't await UpdateChartsWithValuesAsync to complete anymore, raising nasty bugs.

Can anyone explain this behaviour? Thanks

WPF, .NET Framework 4.7 project

Krusty
  • 955
  • 1
  • 12
  • 26
  • I'm no expert, but I think UpdateChartsWithValuesAsync is actually awaited, but on another thread. Try not to mix async/await with tools that were mostly useful before async/await existed (like ContinueWith... I have no idea why you'd want to use it here). – Kilazur Feb 28 '20 at 10:58

3 Answers3

2

Simply put, .ContinueWith() does not do await in its implementation, instead runs the passed in delegate as it is and returns a Task of Task (Task<Task>>). This outer task, because not awaiting on the passed in delegate, completes immediately.

What I suggest, don't use .ContinueWith() in this case, simply stick to await. If you really keen to keep the current code, you can do .ContinueWith().Unwrap(), what will does the trick.

Also here is an another related question in the topic: Use an async callback with Task.ContinueWith

If you wanna learn more in depth, the source code of ContinueWith: https://referencesource.microsoft.com/#mscorlib/system/threading/tasks/Task.cs,4532

  • 2
    Good answer. An alternative to `Unwrap` is awaiting twice: [`await await`](https://stackoverflow.com/questions/34816628/await-await-vs-unwrap.) – Theodor Zoulias Feb 28 '20 at 13:39
1

Dispatcher.InvokeAsync returns a DispatcherOperation. This is an awaitable type, as it implements a method called GetAwaiter() that returns a type that implements INotifyCompletion.

When you use await on the result of Dispatcher.InvokeAsync directly, the completion of DispatcherOperation is awaited before the call to UpdateChartsWithValuesAsync.

In your second example, you do not await this directly; you are awaiting the result of the chained expression:

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

So only the final object (Task) is awaited, meaning the function you pass to ContinueWith may be executed, before Dispatcher.InvokeAsync has completed.

If you are using async / await, it would be prudent to only use the keywords in an async method, as mixing with the callback based operations leads to confusing code such as this.

Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
1

As other answer mentioned, you shouldn't put async t => await UpdateChartsWithValuesAsync in your ContinueWith callback as it would result in awaiting the Task<Task> ContinueWith(...) method, which would eventually finish immediately.

If you really want to wait for ContinueWith to finish in most outer await you should Unwrap() your Task<Task> and await, or use synchronous API inside, but remember about the correct management of the synchronization context.

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);

Note that await is translated into code block involving ContinueWith which features ConfigureAwait options and more, so you better use async await constructions like you tried in your first example.

Jacek
  • 829
  • 12
  • 31