Правильный способ объединения задач с помощью продолжения - PullRequest
0 голосов
/ 14 февраля 2019

Понятие async - await легко понять, однако у меня возникают проблемы с освоением ContinueWith.

. В следующем примере я хочу запустить 2 асинхронные задачи (LoadAsync и ComputeAsync) один за другим, а пока что хочу сделать DoSomethingElse.Метод № 1 - это подход, с которым я знаком.

Методы # 2, # 3 и # 4 верны для достижения того же результата, что и # 1?Может ли кто-нибудь объяснить / прокомментировать различия между сценой?Спасибо!

Метод № 1 - Используйте await внутри async функции

public async int LoadAndComputeAsync
{ 
    var data = await LoadAsync();
    return await ComputeAsync(data);
}

Task<int> task1 = LoadAndComputeAsync();
DoSomethingElse();
int result = await task1;

Метод № 2 - Выполните синхронно в Task.Run

Task<int> task2 = Task.Run(() => {
    var data = LoadAsync().Result;
    return ComputeAsync(data).Result;
});
DoSomethingElse();
int result = await task2;

Метод № 3 - Используйте ContinueWith с async и Unwrap

Task<int> task3 = LoadAsync().ContinueWith(async(t) => {
    var data = t.Result;
    return await ComputeAsync(data);
}).Unwrap();
DoSomethingElse();
int result = await task3;

Метод № 4 - Выполните синхронно в ContinueWith

Task<int> task4 = LoadAsync().ContinueWith(t => {
    var data = t.Result;
    return ComputeAsync(data).Result;
});
DoSomethingElse();
int result = await task4;

Ответы [ 2 ]

0 голосов
/ 14 февраля 2019

Однако у меня проблемы с освоением 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, вам необходимо:

  1. Не допускать включения исключений в AggregateException.
  2. Всегда передавайте явное TaskScheduler в ContinueWith.
  3. Используйте другие подходящие флаги для ContinueWith, чтобы сделать его поведение более асинхронным.
  4. Захватите соответствующий контекст и выполните продолжения в этом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();
}
0 голосов
/ 14 февраля 2019
  1. Большая разница между методом # 2 и другим, который он создал поток с помощью Task.Run async / await никогда не создает новый поток - он выполняет продолжение в том же потоке, из которого был вызван.
  2. Метод № 1 и Метод № 3 почти одинаковый подход
...