В чем разница между Task.Start / Wait и Async / Await?

207

Я могу что-то упустить, но в чем разница между деланием:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Джон
источник

Ответы:

395

Я могу что-то упустить

Ты.

какая разница между деланием Task.Waitи await task?

Вы заказываете обед у официанта в ресторане. Через мгновение после того, как вы отдаете заказ, заходит друг, садится рядом с вами и начинает разговор. Теперь у вас есть два варианта. Вы можете игнорировать своего друга, пока задача не будет завершена - вы можете ждать, пока прибудет ваш суп, и больше ничего не делать, пока ждете. Или вы можете ответить своему другу, и когда ваш друг перестанет говорить, официант принесет вам ваш суп.

Task.Waitблокирует, пока задача не завершена - вы игнорируете своего друга, пока задача не будет завершена. awaitпродолжает обрабатывать сообщения в очереди сообщений, а когда задача завершается, она помещает в очередь сообщение с надписью «забрать, где вы остановились после этого ожидания». Вы разговариваете со своим другом, и когда в перерыве происходит перерыв, прибывает суп.

Эрик Липперт
источник
5
@ronag Нет, это не так. Как бы вы хотели, если бы ожидание a, Taskкоторое занимает 10 мс, фактически выполняло бы 10-часовую работу Taskв вашем потоке, таким образом блокируя вас на все 10 часов?
svick
62
@StrugglingCoder: оператор await ничего не делает, кроме оценки своего операнда и немедленного возврата задачи текущему вызывающему . Люди приходят к мысли, что асинхронность может быть достигнута только путем разгрузки работы на потоки, но это неверно. Вы можете готовить завтрак и читать газету, пока тост находится в тостере, не нанимая повара для наблюдения за тостером. Люди говорят, что внутри тостера должна быть спрятана нить - рабочий, но я вас уверяю, что если вы посмотрите в свой тостер, там нет маленького парня, который смотрит тост.
Эрик Липперт
11
@StrugglingCoder: Итак, кто выполняет работу, которую вы спрашиваете? Может быть, другой поток выполняет работу, и этот поток был назначен ЦПУ, поэтому работа фактически выполняется. Может быть, работа выполняется аппаратно, а потока нет вообще. Но, конечно же, вы говорите, в аппаратном обеспечении должна быть какая-то нить . Нет. Оборудование существует ниже уровня потоков. Там не должно быть никакой темы! Вы могли бы извлечь выгоду из чтения статьи Стивена Клири «Нет потока».
Эрик Липперт
6
@StrugglingCoder: Теперь вопрос, предположим, что выполняется асинхронная работа, нет аппаратного обеспечения и другого потока. Как это возможно? Ну, предположим, что то, что вы ожидали, ставит в очередь серию сообщений Windows , каждое из которых выполняет свою работу? Что теперь происходит? Вы возвращаете управление в цикл обработки сообщений, он начинает извлекать сообщения из очереди, каждый раз выполняя небольшую работу, и последнее задание, которое выполняется, - «выполнить продолжение задачи». Никакой дополнительной темы!
Эрик Липперт
8
@StrugglingCoder: Теперь подумай о том, что я только что сказал. Вы уже знаете, что так работает Windows . Вы выполняете серию движений мыши и нажатий кнопок и еще много чего. Сообщения помещаются в очередь, обрабатываются по очереди, каждое сообщение вызывает крошечный объем работы, и когда все это выполнено, система продолжает работать. Асинхронизация в одном потоке - это не более чем то, к чему вы уже привыкли: разбивать большие задачи на маленькие кусочки, ставить их в очередь и выполнять все маленькие кусочки в некотором порядке. Некоторые из этих казней приводят к постановке в очередь другой работы, и жизнь продолжается. Одна нить!
Эрик Липперт
121

Чтобы продемонстрировать ответ Эрика, вот код:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Джон
источник
27
+1 за код (лучше один раз запустить, чем сто раз прочитать). Но фраза " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" вводит в заблуждение. После нажатия кнопки с t.Wait();обработчиком события нажатия кнопки ButtonClick()невозможно нажать что-либо, а затем увидеть что-то в консоли и обновить метку «до тех пор, пока эта задача не будет выполнена», поскольку графический интерфейс завис и не отвечает, то есть любые щелчки или взаимодействия с графическим интерфейсом. не будучи ПОТЕРЯЛИ до завершения задания ожидания
Геннадий Ванин Геннадий Ванин
2
Я предполагаю, что Эрик предполагает, что у вас есть базовое понимание API Task. Я смотрю на этот код и говорю себе: «yup t.Waitбудет блокировать основной поток, пока задача не будет завершена».
Маффин Ман
50

Этот пример демонстрирует разницу очень четко. При использовании async / await вызывающий поток не блокирует и продолжает выполнение.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

DoAsTask Output:

[1] Начало программы
[1] 1 - Начало
[1] 2 - Задача запущена
[3] A - Начал что-то
[3] B - Завершено что-то
[1] 3 - Задание выполнено с результатом: 123
[1] Конец программы

Выход DoAsAsync:

[1] Начало программы
[1] 1 - Начало
[1] 2 - Задача запущена
[3] A - Начал что-то
[1] Конец программы
[3] B - Завершено что-то
[3] 3 - Задание выполнено с результатом: 123

Обновление: улучшенный пример, показывающий идентификатор потока в выходных данных.

Mas
источник
4
Но если я это сделаю: новая задача (DoAsTask) .Start (); вместо DoAsAsync (); я получаю ту же функцию, так что где вы
ждете?
1
С вашим предложением, результат задачи должен быть оценен где-то еще, может быть, другой метод или лямбда. Функция async-await упрощает отслеживание асинхронного кода. Это просто усилитель синтаксиса.
Мас
@ Я не понимаю, почему Конец Программы после A - Начал что-то. Насколько я понимаю, когда дело доходит до ожидания, процесс ключевого слова должен немедленно перейти к основному контексту, а затем вернуться назад.
@JimmyJimm Насколько я понимаю, Task.Factory.StartNew создаст новый поток для запуска DoSomethingThatTakesTime. Таким образом, нет никакой гарантии, будет ли сначала выполнено завершение программы или A-Started Something.
RiaanDP
@JimmyJimm: я обновил пример, чтобы показать идентификаторы потоков. Как вы можете видеть, «Конец программы» и «А - что-то началось» работают в разных потоках. Так что на самом деле порядок не является детерминированным.
Мас
10

Wait () приведет к запуску потенциально асинхронного кода синхронно. ждать не буду.

Например, у вас есть веб-приложение asp.net. UserA вызывает / getUser / 1 конечную точку. Пул приложений asp.net выберет поток из пула потоков (Thread1), и этот поток выполнит HTTP-вызов. Если вы сделаете Wait (), этот поток будет заблокирован до разрешения http-вызова. Пока он ожидает, если UserB вызывает / getUser / 2, тогда пулу приложений потребуется обслуживать другой поток (Thread2) для повторного вызова http. Вы только что создали (ну, на самом деле, извлекли из пула приложений) другой поток без причины, потому что вы не можете использовать поток 1, он был заблокирован функцией Wait ().

Если вы используете await в Thread1, то SyncContext будет управлять синхронизацией между вызовами Thread1 и http. Просто он будет уведомлять, как только http-вызов завершен. Между тем, если UserB вызывает / getUser / 2, то вы снова будете использовать Thread1 для выполнения http-вызова, потому что он был освобожден после того, как ожидают попадания. Тогда другой запрос может использовать его, даже дальше. После завершения http-вызова (user1 или user2) Thread1 может получить результат и вернуться к вызывающей стороне (клиенту). Тема 1 использовалась для нескольких задач.

Теоман Шипахи
источник
9

В этом примере не так много, практически. Если вы ожидаете задачу, которая возвращается в другой поток (например, вызов WCF) или передает управление операционной системе (например, File IO), await будет использовать меньше системных ресурсов, не блокируя поток.

foson
источник
3

В приведенном выше примере вы можете использовать «TaskCreationOptions.HideScheduler» и значительно изменить метод «DoAsTask». Сам метод не является асинхронным, как это происходит с «DoAsAsync», потому что он возвращает значение «Задача» и помечается как «асинхронный», создавая несколько комбинаций, это то, что он дает мне точно так же, как при использовании «async / await» :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
user8545699
источник