Яка різниця між Task.Start / Wait та Async / Await?


206

Я можу чогось бракувати, але яка різниця між тим, що робити:

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 Ні, це не так. Як би ви цього хотіли, якби очікування, Taskщо займає 10 мс, насправді виконало б 10 годин Taskна вашій нитці, тим самим блокуючи вас цілих 10 годин?
svick

62
@StruggleCoder: Оператор, що очікує, нічого не робить, окрім як оцінити його операнд, а потім негайно поверне завдання поточному абоненту . Люди отримують в голові таке уявлення, що асинхронність може бути досягнута лише за допомогою перевантаження роботи на нитки, але це помилково. Ви можете готувати сніданок і читати папір, поки тост знаходиться в тостері, не наймаючи кухаря для перегляду тостеру. Люди кажуть, що добре, там повинна бути нитка - робоча - прихована всередині тостеру, але я запевняю вас, що якщо ви подивитеся в тостер, там немає маленького хлопця, який дивиться тост.
Ерік Ліпперт

11
@StruggleCoder: Отже, хто виконує задану вам роботу? Можливо, інший потік виконує цю роботу, і цей потік був призначений на процесор, тому робота насправді виконується. Можливо, робота ведеться апаратно, і нитки взагалі немає. Але, безумовно, ви кажете, в апараті має бути якась нитка . Ні. Обладнання існує нижче рівня потоків. Не повинно бути жодної нитки! Ви можете отримати користь, прочитавши статтю Стівена Клірі "Нитки немає".
Ерік Ліпперт

6
@StruggleCoder: Тепер запитання, припустимо, асинхронна робота виконується, а апаратного забезпечення немає, а іншого потоку немає. Як це можливо? Ну, припустимо, те, що ви очікували, чергає ряд віконних повідомлень , кожне з яких робить трохи роботи? Тепер, що відбувається? Ви повертаєте управління до циклу повідомлень, він починає витягувати повідомлення з черги, щоразу виконуючи трохи роботи, і остання виконана робота - «виконати продовження завдання». Без зайвих ниток!
Ерік Ліпперт

8
@StruggleCoder: Подумайте над тим, що я щойно сказав. Ви вже знаєте, що саме так працює 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 завдання. Я дивлюся на цей код і кажу собі: "Юп t.Waitбуде блокувати основний потік, поки завдання не буде виконано".
The Muffin Man

50

Цей приклад дуже чітко демонструє різницю. З асинхронізуванням / очікуванням виклична нитка не блокується і продовжить виконання.

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:

[1] Початок програми
[1] 1 - Початок
[1] 2 - Завдання розпочато
[3] А - Щось почав
[3] В - щось завершено
[1] 3 - Завдання, виконане результатом: 123
[1] Закінчення програми

Вихід DoAsAsync:

[1] Початок програми
[1] 1 - Початок
[1] 2 - Завдання розпочато
[3] А - Щось почав
[1] Закінчення програми
[3] В - щось завершено
[3] 3 - Завдання, виконане результатом: 123

Оновлення: покращений приклад, показавши ідентифікатор потоку у висновку.


4
Але якщо я це роблю: нове завдання (DoAsTask) .Start (); замість DoAsAsync (); я отримую таку ж функціональність, тож де користь чекати ..
omriman12

1
З вашою пропозицією результат завдання необхідно оцінити десь в іншому місці, можливо, іншим методом чи лямбда. Асинхрон-чекає полегшує асинхронний код. Це просто посилювач синтаксису.
Мас

@Якже я не розумію, чому закінчується програма після A - Почалося щось. З мого розуміння, коли йдеться про очікування, що процес ключових слів повинен негайно перейти до основного контексту, а потім повернутися назад.

@JimmyJimm З мого розуміння Task.Factory.StartNew запустить нову нитку для запуску DoSomethingThatTakesTime. Таким чином, немає гарантії, що програму Кінець програми або Що-небудь розпочато що-небудь буде виконано спочатку.
RiaanDP

@JimmyJimm: Я оновив зразок, щоб показати ідентифікатори потоку. Як бачите, "Кінець програми" та "А - щось розпочато" працюють у різних потоках. Тож фактично порядок не є детермінованим.
Мас

10

Зачекати (), призведе до запуску потенційно асинхронного коду синхронізованим способом. чекати не буде.

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

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


9

У цьому прикладі мало, практично. Якщо ви очікуєте завдання, яке повертається в інший потік (наприклад, виклик WCF) або відмовляється від управління операційною системою (наприклад, файл IO), очікуйте, що буде використано менше системних ресурсів, не блокуючи потік.


3

У наведеному вище прикладі ви можете використовувати "TaskCreationOptions.HideScheduler" та значно змінити метод "DoAsTask". Сам метод не є асинхронним, як це відбувається з "DoAsAsync", тому що він повертає значення "Завдання" і позначається як "асинхронізація", створюючи кілька комбінацій, саме це дає мені точно так само, як використання "async / wait" :

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.