C# - Dispatcher.InvokeAsyn c Продолжить, не ожидается - PullRequest
0 голосов
/ 28 февраля 2020

Предполагая, что 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 проекта

Ответы [ 3 ]

1 голос
/ 28 февраля 2020

Как уже упоминалось в другом ответе, вы не должны помещать async t => await UpdateChartsWithValuesAsync в ваш ContinueWith обратный вызов, так как это приведет к ожиданию метода Task<Task> ContinueWith(...), который в конечном итоге немедленно завершит sh.

Если Вы действительно хотите подождать, пока ContinueWith завершит sh в большинстве внешних 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, как вы пытались в первом примере.

1 голос
/ 28 февраля 2020

Проще говоря, .ContinueWith() не выполняет await в своей реализации, вместо этого запускает переданный делегат в том виде, как он есть, и возвращает Task of Task (Task<Task>>). Эта внешняя задача, поскольку она не ожидает передачи делегата, немедленно завершается.

Что я предлагаю, не используйте .ContinueWith() в этом случае, просто придерживайтесь ожидания. Если вы действительно хотите сохранить текущий код, вы можете сделать .ContinueWith().Unwrap(), что сработает.

Также вот еще один связанный вопрос в топи c: Использовать асин c обратный вызов с Task.ContinueWith

Если вы хотите узнать больше подробно, исходный код ContinueWith: https://referencesource.microsoft.com/#mscorlib / system / threading / tasks / Task.cs, 4532

1 голос
/ 28 февраля 2020

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 получит завершено.

Если вы используете asyn c / await, было бы разумно only использовать ключевые слова в методе asyn c, так как смешивание с операциями на основе обратного вызова приводит чтобы сбить с толку такой код.

...