1
©LuxoftTraining2013
06 июля 2013
Тонкости асинхронного
программирования
Dev Labs
2
©LuxoftTraining2013
Эволюция языка C#
3
©LuxoftTraining2013
Sync vs Async
Synchronous
Perform something here and now.
I’ll regain control to execute something else
when it’s done.
4
©LuxoftTraining2013
Sync vs Async
Caller Srv
Foo
42
Caller Srv
Operation finished
Result: 42
FooAsync
Operation Started
Bckgn
Starting background
operation
Notifies
t
t
t0
Sync vs Async
5
©LuxoftTraining2013
Sync vs Async
var webRequest = WebRequest.Create(Url); 
using (var response = webRequest.GetResponse()) 
{ 
    using (var file = new FileStream(FileName, 
         FileMode.OpenOrCreate)) 
    { 
        var length = response.ContentLength; 
        var textWriter = new StreamWriter(file); 
        textWriter.Write(length.ToString()); 
        textWriter.Close(); 
    } 
}
6
©LuxoftTraining2013
Sync vs Async (2)
var webRequest = WebRequest.Create(Url); 
using (var response = await webRequest.GetResponseAsync()) 
{ 
    using (var file = new FileStream(FileName, 
         FileMode.OpenOrCreate)) 
    { 
        var length = response.ContentLength; 
        var textWriter = new StreamWriter(file); 
        await textWriter.WriteAsync(length.ToString()); 
        textWriter.Close(); 
    } 
}
7
©LuxoftTraining2013
Demo – sync 2 async
8
©LuxoftTraining2013
Если вы думаете, что
асинхронное программирование
стало проще!
9
©LuxoftTraining2013
Копайте в глубь!
Все нетривиальные абстракции дырявы
Джоэл Спольски «Закон дырявых абстракций»
Вы должны понимать как минимум на один
уровень абстракции ниже того уровня, на
котором вы кодируете
Ли Кэмпбел (Lee Campbell)
10
©LuxoftTraining2013
Async/await – лишь вершина
11
©LuxoftTraining2013
Synchronization Context
 Некоторые типы приложений налагают
ограничения на «потоковую» модель
 Control.Invoke/BeginInvoke
 Dispatcher.Invoke/BeginInvoke
 Контекст синхронизации «прячет» эти детали за
абстрактным интерфейсом
 Контекст нужен для «маршалинга» управления
из одного потока в другой (*)
 Контексты повсюду!
12
©LuxoftTraining2013
Зачем мне это?
Я же программирую на C#
5.0!
13
©LuxoftTraining2013
private int GetAnswerToUltimateQuestion()
{
    buttonRun.Enabled = false;
    Thread.Sleep(1000);
    buttonRun.Enabled = true;
    return 42;
}
private void buttonRun_Click_Sync(object sender, EventArgs e)
{
    int result = GetAnswerToUltimateQuestion();
    textBoxStatus.Text = result.ToString();
}
Sync Version
Некоторая длительная
операция!!
14
©LuxoftTraining2013
private async Task<int> GetAnswerToUltimateQuestionAsync()
{
    buttonRun.Enabled = false;
    await Task.Delay(1000);
    buttonRun.Enabled = true;
    return 42;
}
private void buttonRun_Click_Async(object sender, EventArgs e)
{
    Task<int> result = GetAnswerToUltimateQuestionAsync();
    textBoxStatus.Text = result.Result.ToString();
}
Наивный подход…
[Thread 2] Ожидает
освобождения UI
потока
[Thread 1] Ожидает
завершения
асинхронной
операции
Захватываем Sync Context
Возвращает
управление!
15
©LuxoftTraining2013
Другими словами…
private Task<int> GetAnswerToUltimateQuestionAsyncImpl()
{
    buttonRun.Enabled = false;
    var task = LongRunningTask();
    return task.ContinueWith(t =>
        {
            buttonRun.Enabled = true;
            return 42;
        }, TaskScheduler.FromCurrentSynchronizationContext());
}
private void buttonRun_Click_Async_Impl(object sender, EventArgs e)
{
    Task<int> result = GetAnswerToUltimateQuestionAsyncImpl();
    textBoxStatus.Text = result.Result.ToString();
}
17
©LuxoftTraining2013
“Sync over Async” + UI ==
Deadlock!
18
©LuxoftTraining2013
Решение
private async Task<int> GetAnswerAsync() 
{ 
    await Task.Delay(10); 
    return 42; 
} 
private async void btn_Click(object sender, 
    EventArgs e) 
{ 
    label.Text = (await GetAnswerAsync()).ToString(); 
}
19
©LuxoftTraining2013
Где вылетит ошибка?
var ms = new MemoryStream();           
// Здесь? 
Task<int> task = ms.ReadAsync(null, 0, 42); 
// Или здесь? 
int result = task.Result;
• Является исключение «синхронным» или «асинхронным»?
20
©LuxoftTraining2013
«Наивная» реализация
public static async Task<int> ReadAsync(byte[] buffer) 
{ 
    if (buffer == null) 
         throw new ArgumentNullException("buffer"); 
    // Реализация асинхронного чтения 
    return 42; 
}
Результирующая задача перейдет
в Faulted состояние!
• Синхронное исключение означает «баг» в вызывающем
коде.
• «Поломанная» задача означает баг в реализации!
Зачем заморачиваться?
21
©LuxoftTraining2013
Корректная реализация
public static Task<int> ReadAsync(byte[] buffer) 
{ 
    if (buffer == null) 
         throw new ArgumentNullException("buffer"); 
    return ReadAsyncImpl(buffer); 
} 
private static async Task<int> ReadAsyncImpl(byte[] buffer) 
{ 
    // Реализация асинхронногочтения 
    return 42; 
}
Синхронная проверка
«предусловий»
Блоки итераторов ведут
себя аналогично!
22
©LuxoftTraining2013
Await исключений
public static async Task<int> SimpleAsync() 
{ 
    throw new CustomException(); 
} 
public static async void ConsumeSimpleAsync() 
{ 
    var task = SimpleAsync(); 
    try 
    { 
        // "Разыменовывание" задачи приводит к 
        // "разворачиванию" первого исключения! 
        int result = await task; 
    } 
    catch (CustomException) 
    { 
        Console.WriteLine("Yahoo!!!"); 
    } 
}
23
©LuxoftTraining2013
Обработка нескольких исключений
public static async Task FooAsync() 
{ 
    // t1 "падает" 
    Task<int> t1 = Task<int>.Factory.StartNew(() => 
    { 
        throw new Exception("E1"); 
    }); 
    // t2 тоже "падает" 
    Task<int> t2 = Task<int>.Factory.StartNew(() => 
    { 
        throw new Exception("E2"); 
    }); 
    int r1 = await t1; 
    int r2 = await t2; 
}
Получим “E1”?
Получим “E2”?
Получим
AggregateException?
UnobservedTaskException!
24
©LuxoftTraining2013
Unobserved Exceptions
 Событие TaskScheduler.UnobservedException
 Генерируется финализатором
 Не вызывается при обращении к
 Result
 Exception
 Вызове Wait
 Поведение зависит от версии .NET Framework
 .NET 4.5 – «умалчивается» (*)
 .NET 4.0 – «ломает» приложение
25
©LuxoftTraining2013
“Решение”
public static async Task FooAsync()
{ 
   // t1 "падает" 
   Task<int> t1 = Task<int>.Factory.StartNew(() => 
   { 
       throw new Exception("E1"); 
   }); 
    // t2 тоже "падает" 
   Task<int> t2 = Task<int>.Factory.StartNew(() => 
   { 
       throw new Exception("E2"); 
   }); 
    // "Наблюдаем" оба исключения 
   var task = Task.WhenAll(t1, t2); 
   await task.ContinueWith(_ => _.Result);     
   int r1 = t1.Result; 
   int r2 = t2.Result;
}
«Объединяем» обе
задачи
await task; пробросил бы лишь
первое исключение!Явно генерируем AggregateException!!!!
26
©LuxoftTraining2013
Структура исключений
AggregateException
task.ContinueWith()
AggregateException
Task.WhenAll(t1, t2);
First
Exception("E1")
t1: Task
Exception("E2")
t2: Task
First Second
await вытянет первое
исключение и получит
AggregateException!
Task.WhenAll(t1, t2);
await 
task.ContinueWith(_ => _.Result);
27
©LuxoftTraining2013
И как это дело ловить?
var task = Modified.FooAsync(); 
try 
{ 
    await task; 
} 
// Для случая более одного исключения 
catch (AggregateException e) 
{ 
    // "Выпрямляем" все исключения 
    int count = e.Flatten().InnerExceptions.Count; 
    Console.WriteLine(
        "Demo2.Modified.FooAsync failed with {0} exceptions", 
        count); 
} 
// Для более простых случаев 
catch (CustomException e) { } 
catch (Exception e) {}
28
©LuxoftTraining2013
Асинхронные методы
 Типы возвращаемого значения асинхронного
метода:
 async void FooAasync() – Fire and Forget (*)
 async Task FooAsync() – (void Foo())
 async Task<T> FooAsync() – (T Foo())
29
©LuxoftTraining2013
Где вылетит ошибка?
private static async void FooAsync()
{
throw new Exception("Ooops!");
}
It Depends!
30
©LuxoftTraining2013
Demo – async void exceptions
31
©LuxoftTraining2013
Задача: убедиться, что метод генерирует
исключение!
32
©LuxoftTraining2013
Naïve Approach
public static async Task FooAsync()
{
//throw new InvalidOperationException("Oops!");
await Task.Delay(42);
}
[Test]
public async void Test_FooAsync_Throws_1()
{
try
{
await FooAsync();
Assert.Fail("InvalidOperation was not thrown");
}
catch (InvalidOperationException)
{ }
}
[Test]
public async void Test_FooAsync_Throws_2()
{
Assert.Throws<InvalidOperationException>(async () => await FooAsync());
}
33
©LuxoftTraining2013
Рабочий вариант!
[Test]
public void Test_FooAsync_Throws()
{
// Sync over Asyc? ;)
ThrowsAsync<InvalidOperationException>(FooAsync).Wait();
}
public async static Task ThrowsAsync<T>(Func<Task> testCode)
where T : Exception
{
try
{
await testCode();
// Если мы сюда попадем, то движок NUnit
// бросит нужный тип исключения
Assert.Throws<T>(() => { });
}
catch (T)
{}
}
34
©LuxoftTraining2013
Async Guidelines
 Async void – это операции вида “fire-and-forget”
 Вызывающий код не может узнать о завершении
асинхронного метода
 Вызывающий код не может обработать
исключения
(вместо этого они попадут в цикл обработки UI
сообщений или «уронят» приложение!)
 Используйте async void только для обработчиков
событий самого высокого уровня.
 Используйте возвращаемые значения!
 Осторожнее с асинхронными лямбда-
выражениями!
35
©LuxoftTraining2013
Сколько же тут всего…
 Влияние TAP на дизайн приложения!
 Обработка исключений
 Unobserved exceptions
 Bugs vs Task Faults
 Гранулярность асинхронных операций
 Testability
 Work stealing
36
©LuxoftTraining2013
Вот этого не надо
- Как вы пишите софт?
- Бац-бац и в продакшн (с).
- Как из синхронного приложения сделать
асинхронное?
- Async/await и готово!
37
©LuxoftTraining2013
Его высочество Async …
не так прост, как кажется;)
38
©LuxoftTraining2013
Что думает по этому поводу Eric Lippert?
Q: C# 5.0 has new feature called async/await. Why
should developers be excited about it?
A: People like me, are excited about this feature
because its a cooperative multitasking with
coroutines implementing using continuation
passing style.
39
©LuxoftTraining2013
Дополнительные ссылки
 pfxteam blog
 C# Async Tips and Tricks Part 2: Async Void
 MVP Summit presentation on async
 Знакомство с асинхронными операциями в C# 5
40
©LuxoftTraining2013
!Благодарю за внимание
?Вопросы
41
©LuxoftTraining2013
IntHR
Luxtown
Информация об учебном центре
www.luxoft-training.ru/about
Расписание
www.luxoft-training.ru/timetable
Каталог курсов
www.luxoft-training.ru/training/catalog_directions
Контакты
www.luxoft-training.ru/contacts
www.facebook.com/TrainingCenterLuxoft
Расписание,
курсы,
тренеры
Условия
обучения,
логистика,
контакты
Luxtown
Информационные ресурсы Luxoft Training

Тонкости асинхронного программирования

Editor's Notes

  • #2 Первый слайд всего курса.
  • #3 Просто для того, чтобы с чего-нибудь начать разговор и подойти к сегодняшней теме я бы хотел немного напомнить историю развития платформы .NET и языка C# . Это даст нам понять то, как же мы докатились до такой жизни. В целом, нас интересует два последних релиза языка C# , которые и дали основной толчок асинхронному программированию на платформе .NET . И да, вам не послышалось и я не оговорился, именно два последних релиза сделали асинхронное программирование таким, каким мы его знаем сегодня, а не один последний релиз. В C# 4.0 ( а точнее в .NET Framework 4.0) появилась TPL , а в C# 5.0 (и .NET 4.5 ) эта модель асинхронного программирования стала повсеместной; и не последнюю роль в этом вопросе сыграли новые возможности языков C# и VB . NET . Давайте посмотрим, к чему же мы стремились.
  • #6 С одной стороны, асинхронное программирование на платформе .NET существовало с самой первой ее версии (в виде так называемой Asynchronous Programming Model ), а с другой стороны асинхронное программирование всегда было на порядок сложнее с точки зрения реализации, отладки и сопровождения, по сравнению с синхронной версией. Основная же цель новых «асинхронных» возможностей языка C# была направлена на «унификацию» синхронного и асинхронного программирования. Решить проблему асинхронности путем добавления пары ключевых слов в тело и заголовок методов, чтобы сделать их асинхронными. И это просто прекрасно, но сразу же после выхода этого дела в свет стало ясно, что у этой простоты есть своя цена! На самом деле, я могу
  • #7 TODO: выделить ключевые слова и Async , чтобы изменения были более видны
  • #9 Главная цель моего выступления заключается в том, чтобы немного сместить акцент с языковых конструкций, которые появлись в C# и VB с выходом VS2012 . Разобраться с новыми ключевыми словами не требует больших усилий, однако для написания нормального асинхронного кода все равно нужно понимать, что происходит внутри. О новых языковых возможностях не рассказывал и не писал только ленивый. В любой новой книге по языку C# , в любом выступлении на конференции об этом говорится. Но уже сейчас становится понятно, что та простота, которой так хотели добиться разработчики обладает и негативной стороной. Единственной сессией на MVP саммите в этом году было выступление Стивена Тауба и Лушиана Вишика о проблемах async void методах. Это первый такой звоночек, который говорит, что слишком многие относятся к новым возможностям слишком уж легкомысленно. Поэтому сегодня я хочу дать основные понятия и концепции, которые стоит изучить более подробно, чтобы стать асинхронным гуру!
  • #10 Абстракции текут и чтобы понимать что-то нам нужно разбираться на один уровень абстракции ниже!
  • #12 Обязательно сказать и продумать про разные типы приложений!!
  • #19 Когда речь заходит за UI потоки, то любой join асинхронной операции может привести к deadlock -у. В этом случае работает следующее правило: если вы начали использовать асинхронные операции, то их следует продолжать использование, а не ожидать их завершения (явно или неявно).
  • #24 Что будет в этом случае? Будет ли проброшено исключение первой задачи, второй задачи, обеих задач в виде AggregateException ?
  • #26 Очень печально, но в данном случае наружу вылетит лишь первое из возникших исключений! Я так и не смог добиться получения наружу обоих исключений!!
  • #35 Principles Async void is a “fire-and-forget” mechanism... The caller is unable to know when an async void has finished The caller is unable to catch exceptions thrown from an async void (instead they get posted to the UI message-loop) Guidance Use async void methods only for top-level event handlers (and their like) Use async Task-returning methods everywhere else When you see an async lambda, verify it
  • #37 Найти рисунок и цитату типа «тяп-ляп и в продакшн»