Асинхронное
программирование в .Net
Сергей Тепляков
STeplyakov@luxoft.com
О Вашем инструкторе


 Сергей Тепляков
 Visual C# MVP, RSDN Team member
 Sergey.Teplyakov@gmail.com
 SergeyTeplyakov.blogspot.com




                                    1-2
Цели курса…
Слушатели изучат:
 Достоинства и недостатки
  синхронного программирования
 Существующие паттерны
  асинхронного программирования на
  платформе .Net
 Использование библиотеки
  PowerThreading
 Использование библиотеки TPL
 Новые возможности C# 5
                                     1-3
Необходимая подготовка
Слушатели должны:

 Быть знакомы с основами языка C#
  и платформы .Net
 Обладать базовыми знаниями
  многопоточности




                                     1-4
Roadmap

 Модель синхронного
  программирования
 Паттерны асинхронного
  программирования на платформе .Net
 Недостатки существующих моделей
 Библиотека PowerThreading
 Библиотека TPL
 C# 5.0: async и await
                                       1-5
Недостатки синхронного
программирования

 Плохая масштабируемость
 Блокирование пользовательского
  интерфейса
 Низкая эффективность операций
  ввода/вывода
 Невозможность использования в
  некоторых контекстах (например, с
  JavaScript и Silverlight)
Плохая масштабируемость.
последовательное выполнение
       sd Sync


                 Client                              HttpWebRequest




                          webRequest1.GetWebResponce()




                             ProcessWebResponce1()



                          webRequest2.GetWebResponce()




                             ProcessWebResponce2()
Плохая масштабируемость

 Неэффективное использование
  мощностей современных многоядерных
  процессоров для CPU-Bound операций
 Неэффективное выполнение IO-Bound
  операций даже для одноядерных
  процессоров
Блокировка пользовательского
интерфейса
Блокировка пользовательского
интерфейса

// Обработчик кнопки получения данных от веб-страниц
private void receiveDataButton_Click(object sender, EventArgs e)
{
    Stopwatch sw = Stopwatch.StartNew();    // 1
    _summaryContentLength = 0;              // 2
    foreach (var url in urls)
    {
        // GetResponse возвращает результат синхронно
        using (WebResponse webResponse = GetResponse(url)) // 3
        {
            ProcessResponse(webResponse);   // 4
            executionTimeTextBox.Text = sw.ElapsedMilliseconds.ToString();
        }
    }
}
А ведь это и не всегда
возможно!

 Некоторые среды, такие как Silverlight и
  JavaScript не поддерживают синхронные
  операции
Рассматриваемые темы

 Модель синхронного программирования
 Паттерны асинхронного
  программирования на платформе
  .Net
 Недостатки существующих моделей
 Библиотека PowerThreading
 Библиотека TPL
 C# 5.0: async и await
Асинхронная модель
программирования

 Два паттерна асинхронного
  программирования
   Classical Async Pattern
   Event-Based Async Pattern
Classical Async Pattern

 Структура паттерна:
// Синхронный метод
public <return> Operation(<parameters>, <out params> )

// Методы классического асинхронного паттерна
public IAsyncResult BeginOperation(<parameters>, AsyncCallback callback,
   object state)
public <return> EndOperation(IAsyncResult asyncResult, <out paramss>)


 Пример:
public WebResponse GetWebResponse(string url, out TimeSpan duration);
public IAsyncResult BeginGetWebResponse(string ulr, object state);
public WebResponse EndGetWebResponse(IAsyncResult ar, out TimeSpan duration);
Classical Async Pattern

 Метод BeginXXX инициирует асинхронную
  операцию
 Метод BeginXXX возвращает IAsyncResult,
  который является маркером асинхронной
  операции
 AsyncCallback представляет собой
  функцию, которая будет вызвана при
  завершении операции
 State представляет собой любой
  пользовательский объект
Classical Async Pattern

 Все by-value и by-ref параметры
  синхронного метода становятся by-value
  параметрами BeginXXX метода
 Тип возвращаемого значения синхронного
  метода совпадает с типом возвращаемого
  значения EndXXX метода
 ref и out параметры синхронного метода
  добавляются в EndXXX метод
 Исключения, генерируемые синхронным
  методом, генерируются методом EndXXX
Примеры Classical Async
Pattern

 Класс System.IO.Stream
public int Read(byte[] buffer, int offset, int count)
public IAsyncResult BeginRead(byte[] buffer, int offset, int count,
    AsyncCallback callback, object state)
public int EndRead(IAsyncResult asyncResult)


 Класс System.Net.WebRequest
public WebResponse GetWebResponse();
public IAsyncResult BeginGetWebResponse(AsyncCallback callback, object state);
public WebResponse EndGetResponse(IAsyncResult asyncResult);
Асинхронное выполнение
sd ClassicAsync


         Form                          Client                             HttpWebRequest         IO completion




                                                webRequest1.BeginGetResponse()


                                                webRequest2.BeginGetResponse()




                                                                 webRequest2.ProcessResponse()




                                                      ProcessResponse()
                  UpdateUIElements()


                                                                 webRequest1.ProcessResponse()



                                                      ProcessResponse()

                  UpdateUIElements()
Classical Async Pattern

 В обработчике UI события:
   Проинициализировать дополнительные поля (теперь без них
    не обойтись)
   Сделать неактивной кнопку
   В цикле начать все операции асинхронно (вызывать метод
    BeginGetResponse)
Classical Async Pattern

 В обработчике завершения асинхронной
  операции:
   Получить WebResponse (вызывать метод EndGetReponse)
   Обработать ответ, не забывая, что обработка происходит не
    в потоке UI
   Понять, что все асинхронные операции завершены (??)
    (посчитать количество завершенных операций)
   Сделать активной кнопку, если все операции таки
    завершены
   Если перед началом выполнения асинхронных операций
    были выделены ресурсы, то освободить их вручную
Разница синхронного и
асинхронного вариантов для
трех веб-узлов
 Среднее выполнение синхронного
  варианта: 520мс
 Среднее выполнение асинхронного
  варианта: 280 мс
 Количество строк кода в синхронном
  решении: 20
 Количество строк кода в асинхронном
  решении: 78
Event-Based Async Pattern
Event-Based Async Pattern
class Class Model


   AsyncCompletedEv entArgs
                                 Находится в пространстве
    +   Canceled: bool           имен: System.ComponentModel                   Событие MethodCompleted
    +   Error: Exception                                                       вызывается как при успешном,
    +   UserState: object                                                      так и при неудачном завершении
                                                                               метода.

                                                                               Метод CancelMethod и событие
                                                                               ProgressChanged являются
                                                                               опциональными.
    MethodCompletedEv entArgs
                                                     SomeType
                                Uses
    +   Arg1: int
                                         +   event MethodCompleted
    +   Arg2: string
                                         +   event ProgressChanged
    +   Result: MethodResult
                                         +   CancelMethod() : void
                Uses
                                         +   MethodAsync(string, int) : void



           MethodResult
Event-Based Async Pattern

 MethodAsync инициирует асинхронную
  операцию
 Есть только одно событие
  MethodCompleted, EventArgs которого
  содержат Result, Error, IsCanceled
 Возможна, но не обязательна,
  поддержка отмены и прогресса
  выполнения
Event-Based Async Pattern

 By-value и ref параметры синхронного
  метода являются входными
  параметрами метода MethodAsync
 Ref и out-параметры становятся
  readonly полями
  MethodCompletedEventArgs
 Событие MethodCompleted
  вызывается в «правильном»
  потоке (в потоке UI для WinForms
  или WPF)
Event-Based Async Pattern

// Функция обработки принятых данных
private void receiveDataButton_Click(object sender, EventArgs e)
{
    // Подготовка операции (отключение кнопки «Принять»,
    // инициализация счетчиков и т.д.
    foreach (var url in urls)
    {
        var webClient = new WebClient();
        webClient.DownloadDataAsync(new Uri(url));

        webClient.DownloadDataCompleted +=
            (s, ev) =>
            {
                // Для обработки ошибки в этом случае нужно
                // обратиться к ствойству ev.Error.
                // Обработка результатов
            };
    }
}
Да как же между ними
выбрать?

 Классический паттерн более
  низкоуровневый и более гибкий
 Event-Based паттерн более простой в
  применении, в частности с UI
  дизайнерами
 Классический паттерн – для кода
  общего назначения, Event-Based – для
  компонентов
 Не применяйте оба паттерна для одного
  класса
Рассматриваемые темы

 Модель синхронного программирования
 Паттерны асинхронного
  программирования на платформе .Net
 Недостатки существующих
  моделей
 Библиотека PowerThreading
 Библиотека TPL
 C# 5.0: async и await
Недостатки моделей
асинхронного программирования

 Непонятный поток исполнения (Control
  Flow)
 Сложность чтения кода и его
  сопровождения
 Сложность обработки ошибок
 Невозможность использования
  привычных языковых конструкций
  (using, try/finally etc)
Рассматриваемые темы

 Модель синхронного программирования
 Паттерны асинхронного
  программирования на платформе .Net
 Недостатки существующих моделей
 Библиотека PowerThreading
 Библиотека TPL
 C# 5.0: async и await
PowerThreading Library

 Разработана Джеффри Рихтер и
  компанией Wintellect
 Содержит класс AsyncEnumerator для
  упрощения работы с асинхронными
  операциями
 Содержит вспомогательные классы для
  работы с многопоточностью (ResourceLock,
  ReaderWriterGate etc)
 Другие вспомогательные классы (Disposer,
  Exception<T>, Singleton etc)
Класс AsyncEnumerator.
Основные концепции

 Поддерживает гибкое управление
  асинхронными операциями (отмену,
  таймауты и т.п.)
 Использует преимущество блока
  итераторов (Iterator block) для
  упрощения потока исполнения
 Использует SynchronizationContext для
  маршалинга потока выполнения в поток
  UI
Класс AsyncEnumerator.
Простой пример
static IEnumerator<int> WorkerMethod()
{
    // Инициируем n асинхронных операций,
    // вызываем BeginRead, BeginGetWebResponse, BeginExecuteCommand etc

   // "возвращаем" n, что говорит AsyncEnumerator-у
   // о количестве запущенных асинхронных операций
   yield return n;

   //   класс AsyncEnumerator вызовет в следующий раз
   //   метод MoveNext нашего енумератора (и мы здесь получим управление)
   //   только после завершения указанного количества асинхронных операций.
   //   Причем, если экземпляр класса AsyncEnumerator-а создавался
   //   с контекстом синхронизации (например, он создавался в потоке UI)
   //   то метод MoveNext будет вызван в потоке UI!

   // Начинаем еще несколько асинхронных операций (например, k)
   yield return k;

}
// Создаем экземпляр енумератора
AsyncEnumerator ae = new AsyncEnumerator();
// Запускаем асинхронные операции, всю грязную работу
// берет на себя AsyncEnumerator
ae.BeginExecute(WorkerMethod(), ae.EndExecute);
Отступление от темы. Блоки
итераторов
static IEnumerator<int> GetNumbers()
{
    string padding = "tt";
    Console.WriteLine(padding + "Первая строка метода GetNumbers()");
    Console.WriteLine(padding + "Сразу перед yield return 7");
    yield return 7;
    Console.WriteLine(padding + "Сразу после yield return 7");
    Console.WriteLine(padding + "Сразу перед yield return 42");
    yield return 42;
    Console.WriteLine(padding + "Сразу после yield return 42");
}
Визуализация итераторов
                                             static IEnumerator<int> GetNumbers()
WriteLine("Вызываем GetNumbers()");          {
IEnumerator<int> iterator = GetNumbers();        string padding = "tt";
                                                 Console.WriteLine(padding + "Первая строка метода GetNumbers()"); // 1
WriteLine("Вызываем MoveNext()...");             Console.WriteLine(padding + "Сразу перед yield return 7"); // 2
bool more = iterator.MoveNext();                 yield return 7; // 3
WriteLine("Result=...", iterator.Current);       Console.WriteLine(padding + "Сразу после yield return 7"); // 4
                                                 Console.WriteLine(padding + "Сразу перед yield return 42"); // 5
WriteLine("Снова вызываем MoveNext()...");       yield return 42; // 6
more = iterator.MoveNext();                      Console.WriteLine(padding + "Сразу после yield return 42"); //7
WriteLine("Result=...", iterator.Current);   }


WriteLine("Снова вызываем MoveNext()...");
more = iterator.MoveNext();
WriteLine("Result={0} (stopping)", more);


Результат:
Вызываем GetNumbers()
Визуализация итераторов
                                               static IEnumerator<int> GetNumbers()
WriteLine("Вызываем GetNumbers()");            {
IEnumerator<int> iterator = GetNumbers();            string padding = "tt";
                                                     Console.WriteLine(padding + "Первая строка метода GetNumbers()");
WriteLine("Вызываем MoveNext()...");                 Console.WriteLine(padding + "Сразу перед yield return 7");
bool more = iterator.MoveNext();                     yield return 7;
WriteLine("Result=...", iterator.Current);           Console.WriteLine(padding + "Сразу после yield return 7");
                                                     Console.WriteLine(padding + "Сразу перед yield return 42");
WriteLine("Снова вызываем MoveNext()...");           yield return 42; // 6
more = iterator.MoveNext();                          Console.WriteLine(padding + "Сразу после yield return 42");
WriteLine("Result=...", iterator.Current);     }

WriteLine("Снова вызываем MoveNext()...");
more = iterator.MoveNext();
WriteLine("Result={0} (stopping)", more);


Результат:
Вызываем GetNumbers()
Вызываем MoveNext()...
                 Первая строка метода GetNumbers()
                 Сразу перед yield return 7
Result=True; Current=7
Визуализация итераторов
                                               static IEnumerator<int> GetNumbers()
WriteLine("Вызываем GetNumbers()");            {
IEnumerator<int> iterator = GetNumbers();          string padding = "tt";
                                                   Console.WriteLine(padding + "Первая строка метода GetNumbers()");
WriteLine("Вызываем MoveNext()...");               Console.WriteLine(padding + "Сразу перед yield return 7");
bool more = iterator.MoveNext();                   yield return 7;
WriteLine("Result=...", iterator.Current);          Console.WriteLine(padding + "Сразу после yield return 7");
                                                    Console.WriteLine(padding + "Сразу перед yield return 42");
WriteLine("Снова вызываем MoveNext()...");          yield return 42;
more = iterator.MoveNext();                         Console.WriteLine(padding + "Сразу после yield return 42");
WriteLine("Result=...“, iterator.Current);     }

WriteLine("Снова вызываем MoveNext()...");
more = iterator.MoveNext();
WriteLine("Result={0} (stopping)", more);


Результат:
Вызываем GetNumbers()
Вызываем MoveNext()...
                Первая строка метода GetNumbers()
                Сразу перед yield return 7
Result=True; Current=7
Снова вызываем MoveNext()...
                 Сразу после yield return 7
                 Сразу перед yield return 42
Result=True; Current=42
Визуализация итераторов
                                              static IEnumerator<int> GetNumbers()
WriteLine("Вызываем GetNumbers()");           {
IEnumerator<int> iterator = GetNumbers();         string padding = "tt";
                                                  Console.WriteLine(padding + "Первая строка метода GetNumbers()");
WriteLine("Вызываем MoveNext()...");              Console.WriteLine(padding + "Сразу перед yield return 7");
bool more = iterator.MoveNext();                  yield return 7;
WriteLine("Result=...", iterator.Current);        Console.WriteLine(padding + "Сразу после yield return 7");
                                                  Console.WriteLine(padding + "Сразу перед yield return 42");
WriteLine("Снова вызываем MoveNext()...");        yield return 42;
more = iterator.MoveNext();                         Console.WriteLine(padding + "Сразу после yield return 42");
WriteLine("Result=...", iterator.Current);    }

WriteLine("Снова вызываем MoveNext()...");
more = iterator.MoveNext();
WriteLine("Result={0} (stopping)", more);


Результат:
Вызываем GetNumbers()
Вызываем MoveNext()...
                Первая строка метода GetNumbers()
                Сразу перед yield return 7
Result=True; Current=7
Снова вызываем MoveNext()...
                Сразу после yield return 7
                Сразу перед yield return 42
Result=True; Current=42
Result=False (stopping)
AsyncEnumerator. Участники

 Класс AsyncEnumerator
 Рабочий метод, возвращающий
  IEnumerator<int>
 Кто-то, связывающий все это
  воедино
AsyncEnumerator
sd AsyncEnumerator


   AsyncEnumerator                           WorkerMethod                             WebRequest   IO Completion




                     calling to MoveNext()


                                                       webRequest1.BeginGetResponse()
                Первый вызов MoveNext
                енумератора
                инициирует
                ас инхронные операции.                 webRequest2.BeginGetResponse()


                                                        WebRequest2Complete()



                                                        WebRequest1Complete()


                          calling to
                          MoveNext()




            Т еперь рабочий метод                      В качес тве метода обработки
            (WorkerMethod) может обработать            завершения ас инхронной
            завершенные операции и начать              операции передаетс я метод
            новые ас инхронные операции.               клас с а AsyncEnumerator-а.
Пример использования класса
AsyncEnumerator
private IEnumerator<int> GetWebData(AsyncEnumerator enumerator)
{
    // Начинаем несколько асинхронных операций
    WebRequest webRequest1 = WebRequest.Create(url1);
    webRequest1.BeginGetResponse(enumerator.End(), null);

    WebRequest webRequest2 = WebRequest.Create(url2);
    webRequest2.BeginGetResponse(enumerator.End(), null);

    yield return 2; // 2 - это количество асинхронных операций

    // Сюда мы попадем уже тогда, когда все асинхронные операции завершены
    WebResponse webResponse1 = webRequest1.EndGetResponse(enumerator.DequeueAsyncResult());
    WebResponse webResponse2 = webRequest2.EndGetResponse(enumerator.DequeueAsyncResult());

    // Обрабатываем полученные результаты аналогичным образом
}
Пример использования класса
AsyncEnumerator
private void receiveDataButton_Click(object sender, EventArgs e)
{
    asyncEnumerator = new AsyncEnumerator();
    // AsyncEnumerator автоматически запоминает контекст синхронизации

    // Запускаем процесс получения данных асинхронно
    asyncEnumerator.BeginExecute(GetWebData(asyncEnumerator),
             asyncEnumerator.EndExecute);
}
Рассматриваемые темы

 Модель синхронного программирования
 Паттерны асинхронного
  программирования на платформе .Net
 Недостатки существующих моделей
 Библиотека PowerThreading
 Библиотека TPL
 C# 5.0: async и await
Библиотека TPL

 Параллелизм задач
 Класс Parallel (Parallel.ForEach etc)
 Parallel LINQ (a.k.a. PLINQ)
Ключевые классы

 Task – инкапсулирует единицу работы
 Task<TResult> - единица работы с определенным
  типом результата
 TaskFactory, TaskFactory<TResult> – фабрики
  создания задач
 TaskScheduler – управляет «расписанием» запуска
  задач
 TaskCompletionSource – управляет «временем жизни»
  задачи
Класс Task<T>

 Представляет собой «незавершенную
  операцию» или «единицу работы»
 Это может быть операция ввода/вывода,
  операция в фоновом потоке, в выделенном
  потоке и т.д.
 Поддерживает «продолжения» с помощью
  ContinueWith
var task2 = task1.ContinueWith(t => … t.Result …);

 Содержит ряд вспомогательных методов:
  WhenAll, WhenAny etc
Примеры использования

1. Простая задача
Task.Factory.StartNew(() => Console.WriteLine("Hello from a task!"));

2. Запуск задачи в выделенном потоке
Task task = Task.Factory.StartNew(
  () =>
     {

        Console.WriteLine("Выполняем длительную операцию");
        Thread.Sleep(TimeSpan.FromHours(1));
        Console.WriteLine("Длительная операция завершена");

     }, TaskCreationOptions.LongRunning);
Примеры использования

3. Получение данных от веб-узла
Task<long> task = new Task<long>(() => {
             var webRequest = WebRequest.Create(url);
             using (var webResponse = webRequest.GetResponse())
             {
                  return webResponse.ContentLength;
             }
         }
);
task.Start();
task.Wait();
long result = task.Result;
4. Использование классического асинхронного API
var webRequest = WebRequest.Create(url);
Task<WebResponse> task = Task<WebResponse>.Factory.FromAsync(
    webRequest.BeginGetResponse, webRequest.EndGetResponse, null);
task.Wait();
var response = task.Result;
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 (ничего не происходит, поскольку задача еще не запущена)
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 (все еще ничего не происходит, поскольку первая задача не запущена)
 (первой задаче установлена вторая задача в виде продолжения)
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 (все еще ничего не происходит, поскольку первая задача не запущена)
 (Связке первых двух задач установлена третья задача в виде продолжения)
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 Запускаем первую задачу
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 Запускаем первую задачу
 Ожидаем завершения цепочки задач
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 (Выполняется первая задача)
 Запускаем первую задачу
 Ожидаем завершения цепочки задач
 Task1...
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 (Выполняется вторая задача, поскольку первая уже завершилась)
 Запускаем первую задачу
 Ожидаем завершения цепочки задач
 Task1...
     Task2...
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");

 Вывод:
 (Выполняется третья задача, поскольку первая и вторая уже завершились)
 Запускаем первую задачу
 Ожидаем завершения цепочки задач
 Task1...
     Task2...
          Task3...
Продолжения

Task task1 = new Task(() => Console.WriteLine("Task1..."));
Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2..."));
Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3..."));

// Запускаем цепочку задач
Console.WriteLine("Запускаем первую задачу");
task1.Start();
Console.WriteLine("Ожидаем завершения цепочки задач");
task3.Wait();
Console.WriteLine("Все задачи завершены");


 Вывод:
 (Выполняется третья задача, поскольку первая и вторая уже завершились)
 Запускаем первую задачу
 Ожидаем завершения цепочки задач
 Task1...
     Task2...
          Task3...
 Все задачи завершены
Рассматриваемые темы

 Модель синхронного программирования
 Паттерны асинхронного
  программирования на платформе .Net
 Недостатки существующих моделей
 Библиотека PowerThreading
 Библиотека TPL
 C# 5.0: async и await
Модель асинхронного
программирования в C# 5

 Аналогична синхронной модели
 Построена по тому же принципу, что и
  класс AsyncEnumerator
 Использует Task<T> для асинхронных
  операций
 Никто не знает, когда выйдет C# 5
 Сейчас доступно Async CTP (Community
  Technology Preview)
Модель асинхронного
программирования в C# 5

 Два новых ключевых слова:
   async – указывает, что метод или лямбда-
    выражение является асинхронным
   await – является аналогом yield return и
    возвращает сразу же управление
    вызывающему коду до тех пор, пока
    асинхронная операция не будет завершена
Модель асинхронного
программирования в C# 5
static async Task<long> GetWebResponseContentLength(string url)
{
    var webRequest = WebRequest.Create(url);
    Console.WriteLine("Перед вызовом await-a. Thread Id: {0}",
        Thread.CurrentThread.ManagedThreadId);

    // Начинаем асинхронную операцию
    Task<WebResponse> responseTask = webRequest.GetResponseAsync();

    // Ожидаем получения ответа
    WebResponse webResponse = await responseTask;

    Console.WriteLine("После завершения await-а. Thread Id: {0}",
        Thread.CurrentThread.ManagedThreadId);

    // В этой строке мы уже получили ответ от веб-узла
    // можем обрабатывать результаты. Тип возвращаемого значения
    // должен соответствовать обобщенному параметру класса Task
    return webResponse.ContentLength;
}
Работа простого метода
WriteLine("Начало исполнения. Thread Id: {0}");    static async Task<long> GetWebResponseContentLength(string url)
                                                   {
Task<long> task =                                      var webRequest = WebRequest.Create(url);
     GetWebResponseContentLength(url);                 WriteLine("Перед вызовом await-a. Thread Id: {0}");

// ожидаем завершения асинхронной операции             // Начинаем асинхронную операцию
task.Wait();                                           Task<WebResponse> respTsk = webRequest.GetResponseAsync();

WriteLine("ContentLength: {0}, Thread Id: {1}");       // Ожидаем получения ответа
                                                       WebResponse webResponse = await respTsk;

                                                       WriteLine("После завершения await-а. Thread Id: {0}");
 Результаты:
 Начало исполнения. Thread Id: 10                      return webResponse.ContentLength;
                                                   }
 Перед вызовом await-a. Thread Id: 10
 (Асинхронная операция запущена)
Работа простого метода
WriteLine("Начало исполнения. Thread Id: {0}");    static async Task<long> GetWebResponseContentLength(string url)
                                                   {
Task<long> task =                                      var webRequest = WebRequest.Create(url);
     GetWebResponseContentLength(url);                 WriteLine("Перед вызовом await-a. Thread Id: {0}");

// ожидаем завершения асинхронной операции             // Начинаем асинхронную операцию
task.Wait();                                           Task<WebResponse> respTsk = webRequest.GetResponseAsync();

WriteLine("ContentLength: {0}, Thread Id: {1}");       // Ожидаем получения ответа
                                                       WebResponse webResponse = await respTsk;

                                                       WriteLine("После завершения await-а. Thread Id: {0}");
 Результаты:                                           return webResponse.ContentLength;
 Начало исполнения. Thread Id: 10                  }

 Перед вызовом await-a. Thread Id: 10
 После завершения await-a. Thread Id: 14
 (Эта строка выполнится только после завершения операции)
Работа простого метода
WriteLine("Начало исполнения. Thread Id: {0}");   static async Task<long> GetWebResponseContentLength(string url)
                                                  {
Task<long> task =                                     var webRequest = WebRequest.Create(url);
     GetWebResponseContentLength(url);                WriteLine("Перед вызовом await-a. Thread Id: {0}");

// ожидаем завершения асинхронной операции            // Начинаем асинхронную операцию
task.Wait();                                          Task<WebResponse> respTsk = webRequest.GetResponseAsync();

WriteLine("ContentLength:{0},Thread Id: {1}");        // Ожидаем получения ответа
                                                      WebResponse webResponse = await respTsk;

                                                      WriteLine("После завершения await-а. Thread Id: {0}");
 Результаты:                                          return webResponse.ContentLength;
                                                  }
 Начало исполнения. Thread Id: 10
 Перед вызовом await-a. Thread Id: 10
 После завершения await-a. Thread Id: 14
 ContentLength: 1672, Thread Id: 10
Сравнение с AsyncEnumerator

 IEnumerator<T> -> async Task<T>
 yield return n -> await task
Модель асинхронного
программирования в C# 5

 Асинхронный метод может возвращать
   void – для асинхронных операций типа “fire
    and forget”
   Task – вызывающий код может дождаться
    завершения асинхронной операции,
    которая не возвращает значения
   Task<T> - для асинхронной операции,
    возвращающей T (string для Task<string>
    etc)
Модель асинхронного
программирования C# 5
private async void receiveDataButton_Click(object sender, EventArgs e)
{
    Stopwatch sw = Stopwatch.StartNew();
    receiveDataButton.Enabled = false;

    IEnumerable<Task<WebResponse>> tasks =
        from url in urls
        let webRequest = WebRequest.Create(url)
        select webRequest.GetResponseAsync();

    // Начинаем выполнять все задачи
    WebResponse[] webResponses = await TaskEx.WhenAll(tasks);

    // Теперь мы можем обработать результаты
    long summaryContentLength = webResponses.Sum(s => s.ContentLength);

    executionTimeTextBox.Text = sw.ElapsedMilliseconds.ToString();
    summaryContentLengthTextBox.Text = summaryContentLength.ToString();
    receiveDataButton.Enabled = true;

    foreach(var wr in webResponses)
        wr.Close();
}
Преимущества новой
асинхронной модели

 Простота использования
 Привычный поток исполнения
 Простота обработки ошибок и
  возможность использования
  конструкций using, try/finally etc
try {
    WebResponse[] data = await TaskEx.WhenAll(tasks);
    // Обработка данных
}
catch (WebException we) {
    //Обработка ошибки получения данных
}
Преимущества новой
асинхронной модели

 Построена на основе проверенных
  идиом (Iterator blocks, AsyncEnumerator,
  Reactive Extensions)
 Построена на основе TPL
  (преимущества от ее использования
  можно закладывать уже сейчас)
Что мы изучили?

 Модель синхронного программирования
 Паттерны асинхронного
  программирования на платформе .Net
 Недостатки существующих моделей
 Библиотека PowerThreading
 Библиотека TPL
 C# 5.0: async и await
Дополнительные ссылки

   Visual Studio Asynchronous Programming (http://msdn.microsoft.com/en-
    us/vstudio/async.aspx)
   Асинхронные операции и AsyncEnumerator
    (http://sergeyteplyakov.blogspot.com/2010/10/asyncenumerator.html)
   «Реактивные расширения и асинхронные операции
    (http://sergeyteplyakov.blogspot.com/2010/11/blog-post.html)
   Знакомство с асинхронными операциями в C# 5
    (http://sergeyteplyakov.blogspot.com/2010/12/c-5.html)
   Джефри Рихтер. Упрощение модели асинхронного программирования с
    помощью AsyncEnumerator (http://msdn.microsoft.com/ru-
    ru/magazine/cc546608.aspx)
   Джеффри Рихтер. Дополнительные возможности AsyncEnumerator
    (http://msdn.microsoft.com/ru-ru/magazine/cc721613.aspx)
Дополнительные ссылки

   Итераторы в языке программирования C#
        Часть 1: http://sergeyteplyakov.blogspot.com/2010/06/c-1.html
        Часть 2: http://sergeyteplyakov.blogspot.com/2010/06/c-2.html
        Часть 3: http://sergeyteplyakov.blogspot.com/2010/06/c-3.html
   Eric Lippert. Continuation Passing Style:
    http://blogs.msdn.com/b/ericlippert/archive/tags/continuation+passing+styl
    e/
   Reactive Extensions Official release
    (http://channel9.msdn.com/Blogs/Charles/Announcing-the-Official-Release-
    of-Rx)
Вопросы?

Luxoft async.net

  • 1.
  • 2.
    О Вашем инструкторе Сергей Тепляков  Visual C# MVP, RSDN Team member  Sergey.Teplyakov@gmail.com  SergeyTeplyakov.blogspot.com 1-2
  • 3.
    Цели курса… Слушатели изучат: Достоинства и недостатки синхронного программирования  Существующие паттерны асинхронного программирования на платформе .Net  Использование библиотеки PowerThreading  Использование библиотеки TPL  Новые возможности C# 5 1-3
  • 4.
    Необходимая подготовка Слушатели должны: Быть знакомы с основами языка C# и платформы .Net  Обладать базовыми знаниями многопоточности 1-4
  • 5.
    Roadmap  Модель синхронного программирования  Паттерны асинхронного программирования на платформе .Net  Недостатки существующих моделей  Библиотека PowerThreading  Библиотека TPL  C# 5.0: async и await 1-5
  • 6.
    Недостатки синхронного программирования  Плохаямасштабируемость  Блокирование пользовательского интерфейса  Низкая эффективность операций ввода/вывода  Невозможность использования в некоторых контекстах (например, с JavaScript и Silverlight)
  • 7.
    Плохая масштабируемость. последовательное выполнение sd Sync Client HttpWebRequest webRequest1.GetWebResponce() ProcessWebResponce1() webRequest2.GetWebResponce() ProcessWebResponce2()
  • 8.
    Плохая масштабируемость  Неэффективноеиспользование мощностей современных многоядерных процессоров для CPU-Bound операций  Неэффективное выполнение IO-Bound операций даже для одноядерных процессоров
  • 9.
  • 10.
    Блокировка пользовательского интерфейса // Обработчиккнопки получения данных от веб-страниц private void receiveDataButton_Click(object sender, EventArgs e) { Stopwatch sw = Stopwatch.StartNew(); // 1 _summaryContentLength = 0; // 2 foreach (var url in urls) { // GetResponse возвращает результат синхронно using (WebResponse webResponse = GetResponse(url)) // 3 { ProcessResponse(webResponse); // 4 executionTimeTextBox.Text = sw.ElapsedMilliseconds.ToString(); } } }
  • 11.
    А ведь этои не всегда возможно!  Некоторые среды, такие как Silverlight и JavaScript не поддерживают синхронные операции
  • 12.
    Рассматриваемые темы  Модельсинхронного программирования  Паттерны асинхронного программирования на платформе .Net  Недостатки существующих моделей  Библиотека PowerThreading  Библиотека TPL  C# 5.0: async и await
  • 13.
    Асинхронная модель программирования  Двапаттерна асинхронного программирования  Classical Async Pattern  Event-Based Async Pattern
  • 14.
    Classical Async Pattern Структура паттерна: // Синхронный метод public <return> Operation(<parameters>, <out params> ) // Методы классического асинхронного паттерна public IAsyncResult BeginOperation(<parameters>, AsyncCallback callback, object state) public <return> EndOperation(IAsyncResult asyncResult, <out paramss>)  Пример: public WebResponse GetWebResponse(string url, out TimeSpan duration); public IAsyncResult BeginGetWebResponse(string ulr, object state); public WebResponse EndGetWebResponse(IAsyncResult ar, out TimeSpan duration);
  • 15.
    Classical Async Pattern Метод BeginXXX инициирует асинхронную операцию  Метод BeginXXX возвращает IAsyncResult, который является маркером асинхронной операции  AsyncCallback представляет собой функцию, которая будет вызвана при завершении операции  State представляет собой любой пользовательский объект
  • 16.
    Classical Async Pattern Все by-value и by-ref параметры синхронного метода становятся by-value параметрами BeginXXX метода  Тип возвращаемого значения синхронного метода совпадает с типом возвращаемого значения EndXXX метода  ref и out параметры синхронного метода добавляются в EndXXX метод  Исключения, генерируемые синхронным методом, генерируются методом EndXXX
  • 17.
    Примеры Classical Async Pattern Класс System.IO.Stream public int Read(byte[] buffer, int offset, int count) public IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) public int EndRead(IAsyncResult asyncResult)  Класс System.Net.WebRequest public WebResponse GetWebResponse(); public IAsyncResult BeginGetWebResponse(AsyncCallback callback, object state); public WebResponse EndGetResponse(IAsyncResult asyncResult);
  • 18.
    Асинхронное выполнение sd ClassicAsync Form Client HttpWebRequest IO completion webRequest1.BeginGetResponse() webRequest2.BeginGetResponse() webRequest2.ProcessResponse() ProcessResponse() UpdateUIElements() webRequest1.ProcessResponse() ProcessResponse() UpdateUIElements()
  • 19.
    Classical Async Pattern В обработчике UI события:  Проинициализировать дополнительные поля (теперь без них не обойтись)  Сделать неактивной кнопку  В цикле начать все операции асинхронно (вызывать метод BeginGetResponse)
  • 20.
    Classical Async Pattern В обработчике завершения асинхронной операции:  Получить WebResponse (вызывать метод EndGetReponse)  Обработать ответ, не забывая, что обработка происходит не в потоке UI  Понять, что все асинхронные операции завершены (??) (посчитать количество завершенных операций)  Сделать активной кнопку, если все операции таки завершены  Если перед началом выполнения асинхронных операций были выделены ресурсы, то освободить их вручную
  • 21.
    Разница синхронного и асинхронноговариантов для трех веб-узлов  Среднее выполнение синхронного варианта: 520мс  Среднее выполнение асинхронного варианта: 280 мс  Количество строк кода в синхронном решении: 20  Количество строк кода в асинхронном решении: 78
  • 22.
  • 23.
    Event-Based Async Pattern classClass Model AsyncCompletedEv entArgs Находится в пространстве + Canceled: bool имен: System.ComponentModel Событие MethodCompleted + Error: Exception вызывается как при успешном, + UserState: object так и при неудачном завершении метода. Метод CancelMethod и событие ProgressChanged являются опциональными. MethodCompletedEv entArgs SomeType Uses + Arg1: int + event MethodCompleted + Arg2: string + event ProgressChanged + Result: MethodResult + CancelMethod() : void Uses + MethodAsync(string, int) : void MethodResult
  • 24.
    Event-Based Async Pattern MethodAsync инициирует асинхронную операцию  Есть только одно событие MethodCompleted, EventArgs которого содержат Result, Error, IsCanceled  Возможна, но не обязательна, поддержка отмены и прогресса выполнения
  • 25.
    Event-Based Async Pattern By-value и ref параметры синхронного метода являются входными параметрами метода MethodAsync  Ref и out-параметры становятся readonly полями MethodCompletedEventArgs  Событие MethodCompleted вызывается в «правильном» потоке (в потоке UI для WinForms или WPF)
  • 26.
    Event-Based Async Pattern //Функция обработки принятых данных private void receiveDataButton_Click(object sender, EventArgs e) { // Подготовка операции (отключение кнопки «Принять», // инициализация счетчиков и т.д. foreach (var url in urls) { var webClient = new WebClient(); webClient.DownloadDataAsync(new Uri(url)); webClient.DownloadDataCompleted += (s, ev) => { // Для обработки ошибки в этом случае нужно // обратиться к ствойству ev.Error. // Обработка результатов }; } }
  • 27.
    Да как жемежду ними выбрать?  Классический паттерн более низкоуровневый и более гибкий  Event-Based паттерн более простой в применении, в частности с UI дизайнерами  Классический паттерн – для кода общего назначения, Event-Based – для компонентов  Не применяйте оба паттерна для одного класса
  • 28.
    Рассматриваемые темы  Модельсинхронного программирования  Паттерны асинхронного программирования на платформе .Net  Недостатки существующих моделей  Библиотека PowerThreading  Библиотека TPL  C# 5.0: async и await
  • 29.
    Недостатки моделей асинхронного программирования Непонятный поток исполнения (Control Flow)  Сложность чтения кода и его сопровождения  Сложность обработки ошибок  Невозможность использования привычных языковых конструкций (using, try/finally etc)
  • 30.
    Рассматриваемые темы  Модельсинхронного программирования  Паттерны асинхронного программирования на платформе .Net  Недостатки существующих моделей  Библиотека PowerThreading  Библиотека TPL  C# 5.0: async и await
  • 31.
    PowerThreading Library  РазработанаДжеффри Рихтер и компанией Wintellect  Содержит класс AsyncEnumerator для упрощения работы с асинхронными операциями  Содержит вспомогательные классы для работы с многопоточностью (ResourceLock, ReaderWriterGate etc)  Другие вспомогательные классы (Disposer, Exception<T>, Singleton etc)
  • 32.
    Класс AsyncEnumerator. Основные концепции Поддерживает гибкое управление асинхронными операциями (отмену, таймауты и т.п.)  Использует преимущество блока итераторов (Iterator block) для упрощения потока исполнения  Использует SynchronizationContext для маршалинга потока выполнения в поток UI
  • 33.
    Класс AsyncEnumerator. Простой пример staticIEnumerator<int> WorkerMethod() { // Инициируем n асинхронных операций, // вызываем BeginRead, BeginGetWebResponse, BeginExecuteCommand etc // "возвращаем" n, что говорит AsyncEnumerator-у // о количестве запущенных асинхронных операций yield return n; // класс AsyncEnumerator вызовет в следующий раз // метод MoveNext нашего енумератора (и мы здесь получим управление) // только после завершения указанного количества асинхронных операций. // Причем, если экземпляр класса AsyncEnumerator-а создавался // с контекстом синхронизации (например, он создавался в потоке UI) // то метод MoveNext будет вызван в потоке UI! // Начинаем еще несколько асинхронных операций (например, k) yield return k; } // Создаем экземпляр енумератора AsyncEnumerator ae = new AsyncEnumerator(); // Запускаем асинхронные операции, всю грязную работу // берет на себя AsyncEnumerator ae.BeginExecute(WorkerMethod(), ae.EndExecute);
  • 34.
    Отступление от темы.Блоки итераторов static IEnumerator<int> GetNumbers() { string padding = "tt"; Console.WriteLine(padding + "Первая строка метода GetNumbers()"); Console.WriteLine(padding + "Сразу перед yield return 7"); yield return 7; Console.WriteLine(padding + "Сразу после yield return 7"); Console.WriteLine(padding + "Сразу перед yield return 42"); yield return 42; Console.WriteLine(padding + "Сразу после yield return 42"); }
  • 35.
    Визуализация итераторов static IEnumerator<int> GetNumbers() WriteLine("Вызываем GetNumbers()"); { IEnumerator<int> iterator = GetNumbers(); string padding = "tt"; Console.WriteLine(padding + "Первая строка метода GetNumbers()"); // 1 WriteLine("Вызываем MoveNext()..."); Console.WriteLine(padding + "Сразу перед yield return 7"); // 2 bool more = iterator.MoveNext(); yield return 7; // 3 WriteLine("Result=...", iterator.Current); Console.WriteLine(padding + "Сразу после yield return 7"); // 4 Console.WriteLine(padding + "Сразу перед yield return 42"); // 5 WriteLine("Снова вызываем MoveNext()..."); yield return 42; // 6 more = iterator.MoveNext(); Console.WriteLine(padding + "Сразу после yield return 42"); //7 WriteLine("Result=...", iterator.Current); } WriteLine("Снова вызываем MoveNext()..."); more = iterator.MoveNext(); WriteLine("Result={0} (stopping)", more); Результат: Вызываем GetNumbers()
  • 36.
    Визуализация итераторов static IEnumerator<int> GetNumbers() WriteLine("Вызываем GetNumbers()"); { IEnumerator<int> iterator = GetNumbers(); string padding = "tt"; Console.WriteLine(padding + "Первая строка метода GetNumbers()"); WriteLine("Вызываем MoveNext()..."); Console.WriteLine(padding + "Сразу перед yield return 7"); bool more = iterator.MoveNext(); yield return 7; WriteLine("Result=...", iterator.Current); Console.WriteLine(padding + "Сразу после yield return 7"); Console.WriteLine(padding + "Сразу перед yield return 42"); WriteLine("Снова вызываем MoveNext()..."); yield return 42; // 6 more = iterator.MoveNext(); Console.WriteLine(padding + "Сразу после yield return 42"); WriteLine("Result=...", iterator.Current); } WriteLine("Снова вызываем MoveNext()..."); more = iterator.MoveNext(); WriteLine("Result={0} (stopping)", more); Результат: Вызываем GetNumbers() Вызываем MoveNext()... Первая строка метода GetNumbers() Сразу перед yield return 7 Result=True; Current=7
  • 37.
    Визуализация итераторов static IEnumerator<int> GetNumbers() WriteLine("Вызываем GetNumbers()"); { IEnumerator<int> iterator = GetNumbers(); string padding = "tt"; Console.WriteLine(padding + "Первая строка метода GetNumbers()"); WriteLine("Вызываем MoveNext()..."); Console.WriteLine(padding + "Сразу перед yield return 7"); bool more = iterator.MoveNext(); yield return 7; WriteLine("Result=...", iterator.Current); Console.WriteLine(padding + "Сразу после yield return 7"); Console.WriteLine(padding + "Сразу перед yield return 42"); WriteLine("Снова вызываем MoveNext()..."); yield return 42; more = iterator.MoveNext(); Console.WriteLine(padding + "Сразу после yield return 42"); WriteLine("Result=...“, iterator.Current); } WriteLine("Снова вызываем MoveNext()..."); more = iterator.MoveNext(); WriteLine("Result={0} (stopping)", more); Результат: Вызываем GetNumbers() Вызываем MoveNext()... Первая строка метода GetNumbers() Сразу перед yield return 7 Result=True; Current=7 Снова вызываем MoveNext()... Сразу после yield return 7 Сразу перед yield return 42 Result=True; Current=42
  • 38.
    Визуализация итераторов static IEnumerator<int> GetNumbers() WriteLine("Вызываем GetNumbers()"); { IEnumerator<int> iterator = GetNumbers(); string padding = "tt"; Console.WriteLine(padding + "Первая строка метода GetNumbers()"); WriteLine("Вызываем MoveNext()..."); Console.WriteLine(padding + "Сразу перед yield return 7"); bool more = iterator.MoveNext(); yield return 7; WriteLine("Result=...", iterator.Current); Console.WriteLine(padding + "Сразу после yield return 7"); Console.WriteLine(padding + "Сразу перед yield return 42"); WriteLine("Снова вызываем MoveNext()..."); yield return 42; more = iterator.MoveNext(); Console.WriteLine(padding + "Сразу после yield return 42"); WriteLine("Result=...", iterator.Current); } WriteLine("Снова вызываем MoveNext()..."); more = iterator.MoveNext(); WriteLine("Result={0} (stopping)", more); Результат: Вызываем GetNumbers() Вызываем MoveNext()... Первая строка метода GetNumbers() Сразу перед yield return 7 Result=True; Current=7 Снова вызываем MoveNext()... Сразу после yield return 7 Сразу перед yield return 42 Result=True; Current=42 Result=False (stopping)
  • 39.
    AsyncEnumerator. Участники  КлассAsyncEnumerator  Рабочий метод, возвращающий IEnumerator<int>  Кто-то, связывающий все это воедино
  • 40.
    AsyncEnumerator sd AsyncEnumerator AsyncEnumerator WorkerMethod WebRequest IO Completion calling to MoveNext() webRequest1.BeginGetResponse() Первый вызов MoveNext енумератора инициирует ас инхронные операции. webRequest2.BeginGetResponse() WebRequest2Complete() WebRequest1Complete() calling to MoveNext() Т еперь рабочий метод В качес тве метода обработки (WorkerMethod) может обработать завершения ас инхронной завершенные операции и начать операции передаетс я метод новые ас инхронные операции. клас с а AsyncEnumerator-а.
  • 41.
    Пример использования класса AsyncEnumerator privateIEnumerator<int> GetWebData(AsyncEnumerator enumerator) { // Начинаем несколько асинхронных операций WebRequest webRequest1 = WebRequest.Create(url1); webRequest1.BeginGetResponse(enumerator.End(), null); WebRequest webRequest2 = WebRequest.Create(url2); webRequest2.BeginGetResponse(enumerator.End(), null); yield return 2; // 2 - это количество асинхронных операций // Сюда мы попадем уже тогда, когда все асинхронные операции завершены WebResponse webResponse1 = webRequest1.EndGetResponse(enumerator.DequeueAsyncResult()); WebResponse webResponse2 = webRequest2.EndGetResponse(enumerator.DequeueAsyncResult()); // Обрабатываем полученные результаты аналогичным образом }
  • 42.
    Пример использования класса AsyncEnumerator privatevoid receiveDataButton_Click(object sender, EventArgs e) { asyncEnumerator = new AsyncEnumerator(); // AsyncEnumerator автоматически запоминает контекст синхронизации // Запускаем процесс получения данных асинхронно asyncEnumerator.BeginExecute(GetWebData(asyncEnumerator), asyncEnumerator.EndExecute); }
  • 43.
    Рассматриваемые темы  Модельсинхронного программирования  Паттерны асинхронного программирования на платформе .Net  Недостатки существующих моделей  Библиотека PowerThreading  Библиотека TPL  C# 5.0: async и await
  • 44.
    Библиотека TPL  Параллелизмзадач  Класс Parallel (Parallel.ForEach etc)  Parallel LINQ (a.k.a. PLINQ)
  • 45.
    Ключевые классы  Task– инкапсулирует единицу работы  Task<TResult> - единица работы с определенным типом результата  TaskFactory, TaskFactory<TResult> – фабрики создания задач  TaskScheduler – управляет «расписанием» запуска задач  TaskCompletionSource – управляет «временем жизни» задачи
  • 46.
    Класс Task<T>  Представляетсобой «незавершенную операцию» или «единицу работы»  Это может быть операция ввода/вывода, операция в фоновом потоке, в выделенном потоке и т.д.  Поддерживает «продолжения» с помощью ContinueWith var task2 = task1.ContinueWith(t => … t.Result …);  Содержит ряд вспомогательных методов: WhenAll, WhenAny etc
  • 47.
    Примеры использования 1. Простаязадача Task.Factory.StartNew(() => Console.WriteLine("Hello from a task!")); 2. Запуск задачи в выделенном потоке Task task = Task.Factory.StartNew( () => { Console.WriteLine("Выполняем длительную операцию"); Thread.Sleep(TimeSpan.FromHours(1)); Console.WriteLine("Длительная операция завершена"); }, TaskCreationOptions.LongRunning);
  • 48.
    Примеры использования 3. Получениеданных от веб-узла Task<long> task = new Task<long>(() => { var webRequest = WebRequest.Create(url); using (var webResponse = webRequest.GetResponse()) { return webResponse.ContentLength; } } ); task.Start(); task.Wait(); long result = task.Result; 4. Использование классического асинхронного API var webRequest = WebRequest.Create(url); Task<WebResponse> task = Task<WebResponse>.Factory.FromAsync( webRequest.BeginGetResponse, webRequest.EndGetResponse, null); task.Wait(); var response = task.Result;
  • 49.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены");
  • 50.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: (ничего не происходит, поскольку задача еще не запущена)
  • 51.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: (все еще ничего не происходит, поскольку первая задача не запущена) (первой задаче установлена вторая задача в виде продолжения)
  • 52.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: (все еще ничего не происходит, поскольку первая задача не запущена) (Связке первых двух задач установлена третья задача в виде продолжения)
  • 53.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: Запускаем первую задачу
  • 54.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: Запускаем первую задачу Ожидаем завершения цепочки задач
  • 55.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: (Выполняется первая задача) Запускаем первую задачу Ожидаем завершения цепочки задач Task1...
  • 56.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: (Выполняется вторая задача, поскольку первая уже завершилась) Запускаем первую задачу Ожидаем завершения цепочки задач Task1... Task2...
  • 57.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: (Выполняется третья задача, поскольку первая и вторая уже завершились) Запускаем первую задачу Ожидаем завершения цепочки задач Task1... Task2... Task3...
  • 58.
    Продолжения Task task1 =new Task(() => Console.WriteLine("Task1...")); Task task2 = task1.ContinueWith(t1 => Console.WriteLine("tTask2...")); Task task3 = task2.ContinueWith(t2 => Console.WriteLine("ttTask3...")); // Запускаем цепочку задач Console.WriteLine("Запускаем первую задачу"); task1.Start(); Console.WriteLine("Ожидаем завершения цепочки задач"); task3.Wait(); Console.WriteLine("Все задачи завершены"); Вывод: (Выполняется третья задача, поскольку первая и вторая уже завершились) Запускаем первую задачу Ожидаем завершения цепочки задач Task1... Task2... Task3... Все задачи завершены
  • 59.
    Рассматриваемые темы  Модельсинхронного программирования  Паттерны асинхронного программирования на платформе .Net  Недостатки существующих моделей  Библиотека PowerThreading  Библиотека TPL  C# 5.0: async и await
  • 60.
    Модель асинхронного программирования вC# 5  Аналогична синхронной модели  Построена по тому же принципу, что и класс AsyncEnumerator  Использует Task<T> для асинхронных операций  Никто не знает, когда выйдет C# 5  Сейчас доступно Async CTP (Community Technology Preview)
  • 61.
    Модель асинхронного программирования вC# 5  Два новых ключевых слова:  async – указывает, что метод или лямбда- выражение является асинхронным  await – является аналогом yield return и возвращает сразу же управление вызывающему коду до тех пор, пока асинхронная операция не будет завершена
  • 62.
    Модель асинхронного программирования вC# 5 static async Task<long> GetWebResponseContentLength(string url) { var webRequest = WebRequest.Create(url); Console.WriteLine("Перед вызовом await-a. Thread Id: {0}", Thread.CurrentThread.ManagedThreadId); // Начинаем асинхронную операцию Task<WebResponse> responseTask = webRequest.GetResponseAsync(); // Ожидаем получения ответа WebResponse webResponse = await responseTask; Console.WriteLine("После завершения await-а. Thread Id: {0}", Thread.CurrentThread.ManagedThreadId); // В этой строке мы уже получили ответ от веб-узла // можем обрабатывать результаты. Тип возвращаемого значения // должен соответствовать обобщенному параметру класса Task return webResponse.ContentLength; }
  • 63.
    Работа простого метода WriteLine("Началоисполнения. Thread Id: {0}"); static async Task<long> GetWebResponseContentLength(string url) { Task<long> task = var webRequest = WebRequest.Create(url); GetWebResponseContentLength(url); WriteLine("Перед вызовом await-a. Thread Id: {0}"); // ожидаем завершения асинхронной операции // Начинаем асинхронную операцию task.Wait(); Task<WebResponse> respTsk = webRequest.GetResponseAsync(); WriteLine("ContentLength: {0}, Thread Id: {1}"); // Ожидаем получения ответа WebResponse webResponse = await respTsk; WriteLine("После завершения await-а. Thread Id: {0}"); Результаты: Начало исполнения. Thread Id: 10 return webResponse.ContentLength; } Перед вызовом await-a. Thread Id: 10 (Асинхронная операция запущена)
  • 64.
    Работа простого метода WriteLine("Началоисполнения. Thread Id: {0}"); static async Task<long> GetWebResponseContentLength(string url) { Task<long> task = var webRequest = WebRequest.Create(url); GetWebResponseContentLength(url); WriteLine("Перед вызовом await-a. Thread Id: {0}"); // ожидаем завершения асинхронной операции // Начинаем асинхронную операцию task.Wait(); Task<WebResponse> respTsk = webRequest.GetResponseAsync(); WriteLine("ContentLength: {0}, Thread Id: {1}"); // Ожидаем получения ответа WebResponse webResponse = await respTsk; WriteLine("После завершения await-а. Thread Id: {0}"); Результаты: return webResponse.ContentLength; Начало исполнения. Thread Id: 10 } Перед вызовом await-a. Thread Id: 10 После завершения await-a. Thread Id: 14 (Эта строка выполнится только после завершения операции)
  • 65.
    Работа простого метода WriteLine("Началоисполнения. Thread Id: {0}"); static async Task<long> GetWebResponseContentLength(string url) { Task<long> task = var webRequest = WebRequest.Create(url); GetWebResponseContentLength(url); WriteLine("Перед вызовом await-a. Thread Id: {0}"); // ожидаем завершения асинхронной операции // Начинаем асинхронную операцию task.Wait(); Task<WebResponse> respTsk = webRequest.GetResponseAsync(); WriteLine("ContentLength:{0},Thread Id: {1}"); // Ожидаем получения ответа WebResponse webResponse = await respTsk; WriteLine("После завершения await-а. Thread Id: {0}"); Результаты: return webResponse.ContentLength; } Начало исполнения. Thread Id: 10 Перед вызовом await-a. Thread Id: 10 После завершения await-a. Thread Id: 14 ContentLength: 1672, Thread Id: 10
  • 66.
    Сравнение с AsyncEnumerator IEnumerator<T> -> async Task<T>  yield return n -> await task
  • 67.
    Модель асинхронного программирования вC# 5  Асинхронный метод может возвращать  void – для асинхронных операций типа “fire and forget”  Task – вызывающий код может дождаться завершения асинхронной операции, которая не возвращает значения  Task<T> - для асинхронной операции, возвращающей T (string для Task<string> etc)
  • 68.
    Модель асинхронного программирования C#5 private async void receiveDataButton_Click(object sender, EventArgs e) { Stopwatch sw = Stopwatch.StartNew(); receiveDataButton.Enabled = false; IEnumerable<Task<WebResponse>> tasks = from url in urls let webRequest = WebRequest.Create(url) select webRequest.GetResponseAsync(); // Начинаем выполнять все задачи WebResponse[] webResponses = await TaskEx.WhenAll(tasks); // Теперь мы можем обработать результаты long summaryContentLength = webResponses.Sum(s => s.ContentLength); executionTimeTextBox.Text = sw.ElapsedMilliseconds.ToString(); summaryContentLengthTextBox.Text = summaryContentLength.ToString(); receiveDataButton.Enabled = true; foreach(var wr in webResponses) wr.Close(); }
  • 69.
    Преимущества новой асинхронной модели Простота использования  Привычный поток исполнения  Простота обработки ошибок и возможность использования конструкций using, try/finally etc try { WebResponse[] data = await TaskEx.WhenAll(tasks); // Обработка данных } catch (WebException we) { //Обработка ошибки получения данных }
  • 70.
    Преимущества новой асинхронной модели Построена на основе проверенных идиом (Iterator blocks, AsyncEnumerator, Reactive Extensions)  Построена на основе TPL (преимущества от ее использования можно закладывать уже сейчас)
  • 71.
    Что мы изучили? Модель синхронного программирования  Паттерны асинхронного программирования на платформе .Net  Недостатки существующих моделей  Библиотека PowerThreading  Библиотека TPL  C# 5.0: async и await
  • 72.
    Дополнительные ссылки  Visual Studio Asynchronous Programming (http://msdn.microsoft.com/en- us/vstudio/async.aspx)  Асинхронные операции и AsyncEnumerator (http://sergeyteplyakov.blogspot.com/2010/10/asyncenumerator.html)  «Реактивные расширения и асинхронные операции (http://sergeyteplyakov.blogspot.com/2010/11/blog-post.html)  Знакомство с асинхронными операциями в C# 5 (http://sergeyteplyakov.blogspot.com/2010/12/c-5.html)  Джефри Рихтер. Упрощение модели асинхронного программирования с помощью AsyncEnumerator (http://msdn.microsoft.com/ru- ru/magazine/cc546608.aspx)  Джеффри Рихтер. Дополнительные возможности AsyncEnumerator (http://msdn.microsoft.com/ru-ru/magazine/cc721613.aspx)
  • 73.
    Дополнительные ссылки  Итераторы в языке программирования C#  Часть 1: http://sergeyteplyakov.blogspot.com/2010/06/c-1.html  Часть 2: http://sergeyteplyakov.blogspot.com/2010/06/c-2.html  Часть 3: http://sergeyteplyakov.blogspot.com/2010/06/c-3.html  Eric Lippert. Continuation Passing Style: http://blogs.msdn.com/b/ericlippert/archive/tags/continuation+passing+styl e/  Reactive Extensions Official release (http://channel9.msdn.com/Blogs/Charles/Announcing-the-Official-Release- of-Rx)
  • 74.