Розуміння async / await у C #


75

Я починаю дізнаватися про async / await у C # 5.0, і я його взагалі не розумію. Я не розумію, як це можна використовувати для паралелізму. Я спробував наступну дуже базову програму:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Task task1 = Task1();
            Task task2 = Task2();

            Task.WaitAll(task1, task2);

            Debug.WriteLine("Finished main method");
        }

        public static async Task Task1()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
            Debug.WriteLine("Finished Task1");
        }

        public static async Task Task2()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
            Debug.WriteLine("Finished Task2");
        }

    }
}

Ця програма просто блокує дзвінок Task.WaitAll()і ніколи не закінчує, але я не розумію, чому. Я впевнений, що мені просто не вистачає чогось простого або просто я не маю правильної розумової моделі цього, і жоден із щоденних блогів чи статей MSDN не допомагає.


5
Замість await new Task...., використовуйтеawait Task.Delay(...);
Девід Хеффернан

Відповіді:


68

Я рекомендую вам розпочати з мого вступу до async/await та подальшої роботи з офіційною документацією MSDN щодо TAP .

Як я вже згадував у своєму вступному дописі в блозі, є декілька Taskчленів, які є переходами від TPL і не мають ніякого використання в чистому asyncкоді. new Taskі його Task.Startслід замінити на Task.Run(або TaskFactory.StartNew). Аналогічним чином Thread.Sleepслід замінити на Task.Delay.

Нарешті, я рекомендую вам не використовувати Task.WaitAll; ваш консольний додаток повинен бути лише Waitна одному, Taskякий використовує Task.WhenAll. З усіма цими змінами ваш код буде виглядати так:

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    public static async Task MainAsync()
    {
        Task task1 = Task1();
        Task task2 = Task2();

        await Task.WhenAll(task1, task2);

        Debug.WriteLine("Finished main method");
    }

    public static async Task Task1()
    {
        await Task.Delay(5000);
        Debug.WriteLine("Finished Task1");
    }

    public static async Task Task2()
    {
        await Task.Delay(10000);
        Debug.WriteLine("Finished Task2");
    }
}

Дякую за вашу відповідь Стівене. Чи можете ви дати мені TL; DR про те, чому очікування Task.WhenAll (...) є кращим за Task.WaitAll ()? Далі, чи можете ви надати мені посилання на будь-яку документацію, яка пояснює, як API, такі як WPF та середовище виконання Windows, знають, як асинхронно виконувати обробники подій?
Alex Marshall

1
Блокування завдань може призвести до глухого кута (посилання на мій блог). У вашому конкретному випадку це було б добре, оскільки Mainметод Console є винятком із настанови "не блокувати". Але я волію відокремлювати винятковий код (використовуючи Wait) від будь-якої логіки, до якої я переходжу MainAsync. Просто набагато менше шансів зайти в глухий кут, якщо / при копіюванні / вставці цього коду в графічний інтерфейс або програму ASP.NET.
Стівен Клірі

2
Асинхронні обробники подій використовують поточний SynchronizationContext. WPF (і я вважаю WinRT) не повинен робити нічого особливого; їх існуючих SynchronizationContextдостатньо для того, щоб asyncобробники подій поводились правильно. Я пояснюю збір / відновлення контексту у своєму вступному дописі, а також у мене є стаття MSDN, яка SynchronizationContextдетально розглядається, якщо вам це цікаво.
Стівен Клірі

15

Зрозумійте завдання C #, асинхронізуйте та очікуйте

C # Завдання

Клас завдань - це асинхронна обгортка завдань. Thread.Sleep (1000) може зупинити потік, що працює протягом 1 секунди. Поки Task.Delay (1000) не зупинить поточну роботу. Дивіться код:

public static void Main(string[] args){
    TaskTest();
}
private static void TaskTest(){
     Task.Delay(5000);
     System.Console.WriteLine("task done");
}

Під час запуску "завдання виконано" з'явиться негайно. Тож я можу припустити, що кожен метод із Task повинен бути асинхронним. Якщо я заміню TaskTest () на Task.Run (() => TaskTest ()), виконане завдання взагалі не відображатиметься, доки я не додаю Console.ReadLine (); після методу Run.

Клас Task внутрішньо представляє стан потоку в машині стану. Кожен стан в автоматі стан має кілька станів, таких як Пуск, Затримка, Скасування та Зупинка.

асинхронізувати і чекати

Тепер ви можете задатися питанням, чи все Task асинхронне, яка мета Task.Delay? далі, давайте реально затримати запущений потік, використовуючи async і await

public static void Main(string[] args){
     TaskTest();
     System.Console.WriteLine("main thread is not blocked");
     Console.ReadLine();
}
private static async void TaskTest(){
     await Task.Delay(5000);
     System.Console.WriteLine("task done");
}

async повідомте абоненту, я асинхронний метод, не чекайте мене. await всередині TaskTest () запитують очікування асинхронного завдання. Тепер, після запуску, програма зачекає 5 секунд, щоб показати текст виконаного завдання.

Скасувати завдання

Оскільки Завдання є автоматом стану, повинен бути спосіб скасувати завдання під час запуску завдання.

static CancellationTokenSource tokenSource = new CancellationTokenSource();
public static void Main(string[] args){
    TaskTest();
    System.Console.WriteLine("main thread is not blocked");
    var input=Console.ReadLine();
    if(input=="stop"){
          tokenSource.Cancel();
          System.Console.WriteLine("task stopped");
     }
     Console.ReadLine();
}
private static async void TaskTest(){
     try{
          await Task.Delay(5000,tokenSource.Token);
     }catch(TaskCanceledException e){
          //cancel task will throw out a exception, just catch it, do nothing.
     }
     System.Console.WriteLine("task done");
}

Тепер, коли програма запущена, ви можете ввести "стоп", щоб скасувати завдання "Затримка".


12

Ваші завдання ніколи не закінчуються, тому що вони ніколи не починають виконуватися.

Я хотів би Task.Factory.StartNewстворити завдання і розпочати його.

public static async Task Task1()
{
  await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
  Debug.WriteLine("Finished Task1");
}

public static async Task Task2()
{
  await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
  Debug.WriteLine("Finished Task2");
}

Як додаткове зауваження, якщо ви насправді просто намагаєтесь зробити паузу в асинхронному методі, немає необхідності блокувати весь потік, просто використовуйте Task.Delay

public static async Task Task1()
{
  await Task.Delay(TimeSpan.FromSeconds(5));
  Debug.WriteLine("Finished Task1");
}

public static async Task Task2()
{
  await Task.Delay(TimeSpan.FromSeconds(10));
  Debug.WriteLine("Finished Task2");
}

1
Навіщо взагалі створювати нові завдання?
Девід Хеффернан

1
@DavidHeffernan ще не отримав можливості додати це до моєї відповіді, але я припустив, що Алекс, можливо, має на увазі щось більш складне.
MerickOWA

@MerickOWA Так, ти маєш рацію, я мав на увазі щось більш складне. Я хотів змоделювати потоки, які блокують введення-виведення та займають багато часу, і вони не пов'язані чисто з процесором.
Alex Marshall

8

Async і await - маркери, які позначають позиції коду, звідки контроль повинен відновитись після завершення завдання (потоку). Ось детальне відео на YouTube, яке демонстративно пояснює концепцію http://www.youtube.com/watch?v=V2sMXJnDEjM

Якщо ви хочете, ви також можете прочитати цю статтю про співпроект, де це пояснюється більш наочно. http://www.codeproject.com/Articles/599756/Five-Great-NET-Framework-4-5-Features#Feature1:- “Асинхронізація” та “Чекаю” (Кодемарки)

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