Асинхронность в NET или потока нет.pptx
- Количество слайдов: 27
Асинхронность в. Net или потока нет! АЛЕКСАНДР ЧИЧЕРСКИЙ СКБ КОНТУР
О чем этот доклад? § об асинхронном программировании § о том, что происходит под капотом async/await § о типичных ошибках при написании асинхронного кода § о практиках и инструментах, помогающих эти ошибки избегать
Потока нет!
Асинхронность != многопоточность
Идеальные условия для приложения § количество потоков == количеству ядер § каждый поток 100% времени занимается полезной работой и не простаивает в ожидании
Потока нет! § приложение вызывает асинхронный метод в BCL § при запуске асинхронной операции BCL делает системный вызов через Win. API (см. Overlapped IO) § ОС создает I/O Request Packet (IRP) и передает его драйверу устройства § Драйвер отдает команду устройству и помечает пакет “ожидающим обработки” Важно: драйвер устройства не может блокировать поток во время обработки IRP. Если операция не может быть выполнена моментально, она обязана быть выполнена асинхронно.
Потока нет! § Драйвер возвращает управление ОС § ОС возвращает управление BCL § BCL возвращает незавершенный Task приложению § Асинхронный метод приложения прерывается и освободившийся поток переключается на выполнение новой задачи Важно: пока устройство выполняет I/O операцию поток выполняет другую полезную работу
Потока нет! § устройство уведомляет CPU о завершении асинхронной операции через прерывание § Interrupt Service Routine драйвера реагирует на прерывание и создает Deferred Procedure Call (DPC) § DPC получает IRP и помечает его завершенным, работа драйвера кончается § ОС на завершение обработки IRP создает Asynchronous Procedure Call (APC) § APC через IO completion ports уведомляет BCL о завершении асинхронной операции § BCL вызывает callback приложения или помечает Task завершенным
Типичный асинхронный код § синхронное исполнение пролога метода § вызов асинхронной операции § практически моментальный возврат управления и выход из метода или синхронное продолжение его исполнения § * по завершении выполнения асинхронной операции вызов callback’а
Типичный асинхронный код Профит: § отзывчивость приложения с точки зрения UI § увеличение пропускной способности с точки зрения server side Проблемы: § технологические особенности сильно перемешаны с кодом бизнес логики § сложно отследить поток исполнения бизнес логики в лапше из callback’ов
async/await в. Net >= 4. 5 Теплый ламповый асинхронный код в. Net >= 1. 1 ?
Асинхронный код с async/await Имеет все плюсы типичного асинхронного кода: § позволяет создавать отзывчивый UI § помогает увеличить пропускную способность server side Но основное достоинство: § позволяет писать асинхронный код в синхронном стиле
Под капотом у async/await
Под капотом у async/await Ключевое слово async: § говорит компилятору о необходимости специальной кодогенерации (создается стэйт-машина) § прозрачно “упаковывает” результат выполнения метода или исключение в Task Ключевое слово await: § “распаковывает” результат или исключение из Task’а § является возможной точкой выхода из метода
Под капотом у async/await § async/await – синтаксический сахар, позволяющий транслировать код в синхронном стиле в близкий к “классическому” асинхронному § всё исполнение метода разбивается ключевыми словами await на синхронный пролог и набор task’ов запускающихся один по окончании другого. § исполнение метода может оказаться полностью синхронным § пролог асинхронного метода исполняется синхронно по стэку вызовов до первой “настоящей” асинхронной операции
Ошибки в асинхронном коде
Ошибки: async void Проблемы: § теряем возможность узнать об окончании асинхронной операции § создается ложное ощущение работы с синхронным кодом § может обрушить приложение в случае исключения внутри асинхронного метода
Ошибки: async void Причины: § представляет собой fire & forget § поскольку у клиентского кода нет никакой возможности получить доступ к исключению, возникающему внутри async void метода, и обработать его, единственной стратегией остается обрушить приложение
Ошибки: async void Решение: § не использовать async void § всегда возвращать из асинхронного метода Task, который может быть обработан клиентским кодом § если есть необходимость в async void, не забывать об обработке исключений внутри асинхронных методов
Ошибки: async лямбды § Проблема возникнет у лямбды с типом делегата Action или Action<> § Проблема абсолютно аналогичны async void, т. к. Action == void Решение: § обращать внимание на тип делегата у асинхронной лямбды § не использовать async void
Ошибки: необработанный Task § представляет собой почти fire & forget § создается ложное ощущение работы с синхронным кодом § теряем исключения внутри асинхронного метода или узнаем о них слишком поздно § может обрушить приложение при Throw. Unobserved. Task. Exceptions == true
Ошибки: необработанный Task Решение: § дожидаемся завершения асинхронных операций § подписываемся на событие Task. Scheduler. Unobserved. Task. Exception и обрабатываем ошибки
Ошибки: блокировка при ожидании Task’a Проблемы: § возможны deadlock’и на однопоточных контекстах § происходит избыточное потребление ресурсов, т. к. ожидающий поток простаивает в бездействии Решение: § не смешивать синхронный и асинхронный код
Ошибки: отсутствие Configure. Await() Поведение: § отсутствие ==. Configure. Await(true) Проблемы: § возможны deadlock’и на однопоточных контекстах § использование избыточных ресурсов Решение: § не смешивать синхронный и асинхронный код § не забывать. Configure. Await(false) в большинстве сценариев § использовать плагины к Resharper, Style. Cop, анализаторы Roslyn, etc.
Ошибки: коротко § Кэширование результатов асинхронного метода Поведение: кэширование таски вместо результата Проблема: если внутри таска произошло исключение, оно будет закэшировано и проявится сильно позже момента возникновения Решение: не кэшировать таску, в кэш складывать только результат её выполнения § Доступ к Thread static данным (Http. Context, User. Context, Request. Context и т. п. ) Поведение: хранение контекста в Thread. Static переменных Проблема: т. к. выполнение продолжения метода в большинстве случаев может быть запущено на произвольном потоке из пула, Thread. Static данные могут быть утеряны Решение: для проброса глобальных даннных использовать Call. Context. Logical. Get. Data(key) и Call. Context. Logical. Set. Data(key, value)
Выводы § асинхронный код позволяет создавать отзывчивый UI и увеличивать пропускную способность backend’а, т. к. на время выполнения асинхронной (I/O) операции отпускает поток § async/await – отличный синтаксический сахар, позволяющий писать асинхронный код в синхронном стиле § несмотря на синтаксическую простоту, нельзя забывать об особенностях выполнения асинхронного кода, чтобы избежать большинства частых ошибок § всем асинхронность!
Спасибо за внимание! Вопросы?