Его Высочество Async Сергей Тепляков, Visual C# MVP .NET Architect at Luxoft SergeyTeplyakov.blogspot.com
Эволюция языка C#
Sync vs Async Synchronous Perform something here and now. I’ll regain control to execute something else when it’s done.
Asynchronous
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(); } }
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(); } }
Если вы думаете, что асинхронное программирование стало проще!
Копайте в глубь! Все нетривиальные абстракции дырявы Джоэл Спольски «Закон дырявых абстракций»
Вы должны понимать как минимум на один уровень абстракции ниже того уровня, на котором вы кодируете Ли Кэмпбел (Lee Campbell)
Async/await – лишь вершина
Synchronization Context • Некоторые типы приложений налагают ограничения на «потоковую» модель • Control.Invoke/BeginInvoke • Dispatcher.Invoke/BeginInvoke
• Контекст синхронизации «прячет» эти детали за абстрактным интерфейсом • Контекст нужен для «маршалинга» управления из одного потока в другой (*) • Контексты повсюду!
Зачем мне это? Я же программирую на C# 5.0!
Что будет в этом случае? Захватываем Sync Context
private async Task<int> FooAsync() { Возвращает await Task.Delay(10); управление! return 42; Ожидает освобождения } UI потока private void btn_Click(object sender, EventArgs e) { label.Text = FooAsync().Result.ToString(); } Ожидает завершения асинхронной операции
“Sync over Async” + UI == Deadlock!
Решение private async Task<int> FooAsync() { await Task.Delay(10); return 42; } private async void btn_Click(object sender, EventArgs e) { label.Text = (await FooAsync()).ToString(); }
Обработка нескольких исключений public static async Task FooAsync() { // t1 "падает" Task<int> t1 = Task<int>.Factory.StartNew(() => { throw new Exception("E1"); Получим “E1”? }); // t2 тоже "падает" Task<int> t2 = Task<int>.Factory.StartNew(() => { throw new Exception("E2"); Получим “E2”? });
int r1 = await t1; int r2 = await t2; }
Получим AggregateException? UnobservedTaskException!
Unobserved Exceptions • Событие TaskScheduler.UnobservedException • Генерируется финализатором • Не вызывается при обращении к • Result • Exception • Вызове Wait
• Поведение зависит от версии .NET Framework • .NET 4.5 – «умалчивается» (*) • .NET 4.0 – «ломает» приложение
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!!!"); } }
“Решение” 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!!!!
И как это дело ловить? 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) {}
Где вылетит ошибка? var ms = new MemoryStream(); // Здесь? Task<int> task = ms.ReadAsync(null, 0, 42); // Или здесь? int result = task.Result;
• Является исключение «синхронным» или «асинхронным»?
«Наивная» реализация public static async Task<int> ReadAsync(byte[] buffer) { Результирующая задача перейдет в Faulted состояние! if (buffer == null) throw new ArgumentNullException("buffer"); // Реализация асинхронного чтения return 42; }
Зачем заморачиваться?
• Синхронное исключение означает «баг» в вызывающем коде. • «Поломанная» задача означает баг в реализации!
Корректная реализация 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; }
Сколько же тут всего… • Влияние TAP на дизайн приложения! • Обработка исключений • Unobserved exceptions • Bugs vs Task Faults
• • • •
Гранулярность асинхронных операций Testability Work stealing Async Programming Guidelines • • • • • •
Avoid “async void” Avoid async lambdas Consider performance impact Avoid “Sync over Async” Avoid “Async over Sync” Consider using ConfigureAwait
Вот этого не надо - Как вы пишите софт? - Бац-бац и в продакшн (с). - Как из синхронного приложения сделать асинхронное? - Async/await и готово!
Его высочество Async … не так прост, как кажется;)
Вопросы?
Спасибо за внимание • Сергей Тепляков, Visual C# MVP • .NET Architect at Luxoft • Sergey.Teplyakov@gmail.com • http://sergeyteplyakov.blogspot.com/
Что думает по этому поводу 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.
Task vs Observables
TaskCompletionSource фасад к старому асинхронному миру!
Пример public class CustomProvider { public static void ExecuteOperation(Operation operationId, Action<CustomData> action, Action<Exception> func); } public Task<CustomData> GetCustomDataAsync() { var tcs = new TaskCompletionSource<CustomData>(); // Начинаем асинхронную операцию, и передаем // два специализированных делегата CustomProvider.ExecuteOperation(Operation.ReadCustomer, data => tcs.SetResult(data), e => tcs.SetException(e)); return tcs.Task; }