Подтвердить что ты не робот

Когда использовать ключевое слово "ожидание"

Я пишу веб-страницу, и она вызывает некоторые веб-службы. Вызовы выглядели так:

var Data1 = await WebService1.Call();
var Data2 = await WebService2.Call();
var Data3 = await WebService3.Call();

Во время просмотра кода кто-то сказал, что я должен изменить его на:

var Task1 = WebService1.Call();
var Task2 = WebService2.Call();
var Task3 = WebService3.Call();

var Data1 = await Task1;
var Data2 = await Task2;
var Data3 = await Task3;

Почему? Какая разница?

4b9b3361

Ответ 1

В первом фрагменте кода вы даже не запускаете второй вызов службы до тех пор, пока не завершится первый вызов службы (а также не начнется третий до завершения второго). Короче говоря, они выполняются последовательно.

Во втором фрагменте вы запускаете все три вызова службы, но затем не продолжайте в коде до тех пор, пока все три не будут выполнены. Короче говоря, все они выполняются параллельно.

Если второй/третий вызовы не могут быть запущены до тех пор, пока они не будут получены в результате предыдущей операции, вам нужно будет сделать что-то вроде первого фрагмента, чтобы заставить его работать. Если вызовы службы не зависят друг от друга вообще, вы хотите, чтобы они выполнялись параллельно по соображениям производительности.

Если по какой-то причине вам не нравятся дополнительные локальные переменные, существуют другие способы параллельного выполнения задач с использованием альтернативных синтаксисов. Один из вариантов, который будет действовать как ваш второй вариант:

var Data = await Task.WhenAll(WebService1.Call(), 
    WebService2.Call(), 
    WebService3.Call());

Ответ 2

Правильный ответ правильный; немного расшириться. Какая разница между:

Eat(await cook.MakeSaladAsync());
Eat(await cook.MakeSoupAsync());
Eat(await cook.MakeSandwichAsync());

и

Task<Salad> t1 = cook.MakeSaladAsync();
Task<Soup> t2 = cook.MakeSoupAsync();
Task<Sandwich> t3 = cook.MakeSandwichAsync();
Eat(await t1);
Eat(await t2);
Eat(await t3);

?

Первое:

  • Повар, пожалуйста, сделайте мне салат.
  • В ожидании салата у вас есть свободное время, чтобы почистить кошку. Когда вы закончите это, о, смотрите, салат сделан. Если повар закончил салат, когда вы чистили кошку, они не начали делать суп, потому что вы еще не просили его.
  • Ешьте салат. Повар теперь бездействует, пока вы едите.
  • Повар, пожалуйста, сделайте мне суп.
  • В ожидании супа у вас есть свободное время, чтобы очистить аквариум. Когда вы закончите это, о, смотрите, суп сделан. Если повар заканчивает суп, когда вы очищаете аквариум, они не начинаются на сэндвиче, потому что вы еще не просили его.
  • Ешьте суп. Повар теперь бездействует, пока вы едите.
  • Повар, пожалуйста, сделайте мне сэндвич.
  • Снова найдите что-нибудь еще, пока вы ждете.
  • Ешьте сэндвич.

Ваша вторая программа эквивалентна:

  • Повар, пожалуйста, сделайте мне салат.
  • Повар, пожалуйста, сделайте мне суп.
  • Повар, пожалуйста, сделайте мне сэндвич.
  • Сделан ли салат? Если нет, ожидая салата, у вас есть свободное время, чтобы почистить кошку. Если повар закончил салат, пока вы чистили кошку, они начали делать суп.
  • Ешьте салат. Повар все еще может работать на супе и бутерброде, пока вы едите.
  • Готово ли суп?...

Вы видите разницу? В своей первоначальной программе вы не говорите повару, чтобы начать следующий курс, пока не закончите есть первый курс. В вашей второй программе вы запросите все три курса вперед и съедите их - по порядку - по мере их поступления. Вторая программа лучше использует время приготовления, потому что повар может "ускользнуть" от вас.

Ответ 3

Servy опубликовал очень хороший ответ, но вот визуальное описание с помощью Task, чтобы показать, в чем проблема. Этот код не будет функционировать так же, как ваш (он не выполняет все компоненты контекста синхронизации, такие как возврат контроля над насосом сообщений), но это очень хорошо иллюстрирует проблему.

Ваш код делает что-то вроде этого

var fooTask = Task.Factory.StartNew(Foo);
fooTask.Wait();
var fooResult = fooTask.Result;

var barTask = Task.Factory.StartNew(Bar);
barTask.Wait();
var barResult = barTask.Result;

var bazTask = Task.Factory.StartNew(Baz);
bazTask.Wait();
var bazResult = bazTask.Result;

и исправленный код делает что-то вроде этого

var fooTask = Task.Factory.StartNew(Foo);
var barTask = Task.Factory.StartNew(Bar);
var bazTask = Task.Factory.StartNew(Baz);

fooTask.Wait();
var fooResult = fooTask.Result;
barTask.Wait();
var barResult = barTask.Result;
bazTask.Wait();
var bazResult = bazTask.Result;

Вы можете видеть, что все 3 задачи запущены в ожидании возврата первого результата, где в первом примере вторая задача не запускается до завершения первой задачи.