Однако у меня проблемы с освоением ContinueWith
.
Самый простой способ использования ContinueWith
пишется как "ожидание".
Нет, серьезно.ContinueWith
- это низкоуровневый API с удивительным поведением по умолчанию .Это сделает ваш код более сложным и намного более сложным в обслуживании, но не принесет никакой пользы.Итак, мой вопрос «почему?»
Тем не менее, следующее даст вам некоторые ответы, но они предназначены только для учебных целей и , а не производственного кода.
Во-первых, Task<T>.Result
имеет другое поведение обработки исключений;он обернет все исключения в AggregateException
вместо непосредственного их вызова.Это потому, что Task<T>
изначально был разработан для параллельного программирования, а не асинхронного программирования;но когда были добавлены async
/ await
, Microsoft решила просто повторно использовать существующие типы Task
/ Task<T>
вместо создания более асинхронного собственного типа.Для асинхронного кода замените .Result
на .GetAwaiter().GetResult()
.
Далее, async
не ставит работу в очередь в пул потоков.Task.Run
делает.Это еще одно отличие метода № 2.
Ваш метод № 3 довольно близок.Если вы замените .Result
на .GetAwaiter().GetResult()
, у вас все равно будет проблема TaskScheduler
, используемого ContinueWith
, который по умолчанию равен TaskScheduler.Current
, что может быть не тем, что вы хотите (в асинхронном коде это обычноне).Вы никогда не должны использовать ContinueWith
без указания TaskScheduler
.Кроме того, странно использовать ContinueWith
с async
- почему бы просто не использовать метод # 1, если вы все равно используете async
?
Метод # 4 блокирует поток в ContinueWith
.
Если вы хотите получить точное воспроизведение метода № 1, вам необходимо:
- Не допускать включения исключений в
AggregateException
. - Всегда передавайте явное
TaskScheduler
в ContinueWith
. - Используйте другие подходящие флаги для
ContinueWith
, чтобы сделать его поведение более асинхронным. - Захватите соответствующий контекст и выполните продолжения в этомcontext.
Вот пример:
// Original
public async Task<int> LoadAndComputeAsync()
{
var data = await LoadAsync();
return await ComputeAsync(data);
}
// Using ContinueWith
public Task<int> LoadAndComputeTheHardWayAsync()
{
var scheduler = SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current;
return LoadAsync().ContinueWith(t =>
{
var data = t.GetAwaiter().GetResult();
return ComputeAsync(data);
},
CancellationToken.None,
TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously,
scheduler).Unwrap();
}