Асинхронное программирование на языке C#
Руслан Гибадуллин (КНИТУ-КАИ)

В данной заметке речь пойдет об асинхронном программировании и будут раскрыты темы:
1) Принципы асинхронности
2) Что собой представляет асинхронное программирование?
3) Важность языковой поддержки для упрощения присоединения признаков продолжения
4) О том, в какой функциональный эквивалент разворачивает компилятор выражение с ключевым словом await
5) Примеры реализаций асинхронного подхода в пользовательском интерфейсе

Ссылки на презентацию:
1) Present using Windows https://bitbucket.org/landwatersun/foru … 0081400.7z
2) Present using Mac https://bitbucket.org/landwatersun/foru … 081401.zip
3) Portable Document Format https://bitbucket.org/landwatersun/foru … 0081402.7z
4) Prezi https://prezi.com/p/h4picj_r_8nv/

Сравнение синхронных и асинхронных операций
Синхронная операция выполняет свою работу перед возвратом управления вызывающему коду. Асинхронная операция выполняет (большую часть или всю) свою работу после возврата управления вызывающему коду. Большинство создаваемых и вызываемых вами методов будут синхронными. Примерами могут служить List<T>.Add, Console.WriteLine и Thread.Sleep. Асинхронные методы менее распространены и инициируют параллелизм, т.к. они продолжают работать параллельно с вызывающим кодом. Асинхронные методы обычно быстро (или немедленно) возвращают управление вызывающему компоненту, потому их также называют неблокирующими методами. Большинство асинхронных методов, которые мы видели до сих пор, могут быть описаны как универсальные методы:
> Thread.Start;
> Task.Run;
> методы, которые присоединяют признаки продолжения к задачам.

Что собой представляет асинхронное программирование?
Принцип асинхронного программирования состоит в том, что длительно выполняющиеся (или потенциально длительно выполняющиеся) функции реализуются асинхронным образом. Он отличается от традиционного подхода синхронной реализации длительно выполняющихся функций с последующим их вызовом в новом потоке или в задаче для введения параллелизма по мере необходимости. Отличие от синхронного подхода в том, что параллелизм инициируется внутри длительно выполняющейся функции, а не за ее пределами. В результате появляются два преимущества.
> Параллельное выполнение с интенсивным вводом-выводом может быть реализовано без связывания потоков, улучшая показатели масштабируемости и эффективности.
> Обогащенные клиентские приложения в итоге содержат меньше кода в рабочих потоках, что упрощает достижение безопасности в отношении потоков.

В свою очередь это приводит к двум различающимся сценариям использования асинхронного программирования. Первый из них связан с написанием (обычно серверных) приложений, которые эффективно обрабатывают большой объем параллельных операций ввода-вывода. Проблемой здесь является не обеспечение безопасности к потокам (т.к. разделяемое состояние обычно минимально), а достижение эффективности потоков; в частности, отсутствие потребления одного потока на сетевой запрос. Следовательно, в таком контексте выигрыш от асинхронности получают только операции с интенсивным вводом-выводом.

Второй сценарий применения касается упрощения поддержки безопасности в от­ ношении потоков внутри обогащенных клиентских приложений. Он особенно актуален с ростом размера программы, поскольку для борьбы со сложностью мы обычно проводим рефакторинг крупных методов в методы меньших размеров, получая в результате цепочки методов, которые вызывают друг друга (графы вызовов).

Если любая операция внутри традиционного графа синхронных вызовов является длительно выполняющейся, тогда мы должны запускать целый граф вызовов в рабочем потоке, чтобы обеспечить отзывчивость пользовательского интерфейса. Таким образом, мы в конечном итоге получаем единственную параллельную операцию, которая охватывает множество методов (крупномодульный параллелизм), что требует учета безопасности к потокам для каждого метода в графе.

В случае графа асинхронных вызовов мы не должны запускать поток до тех пор, пока это не станет действительно необходимым – как правило, в нижней части графа (или вообще не запускать поток для операций с интенсивным вводом-выводом). Все остальные методы могут выполняться полностью в потоке пользовательского интерфейса со значительно упрощенной поддержкой безопасности в отношении потоков. В результате получается мелкомодульный параллелизм – последовательность небольших параллельных операций, между которыми выполнение возвращается в поток пользовательского интерфейса.

Чтобы извлечь из этого выгоду, операции с интенсивным вводом-выводом и интенсивными вычислениями должны быть реализованы асинхронным образом; хорошее эмпирическое правило предусматривает асинхронную реализацию любой операции, выполнение которой может занять более 50 миллисекунд. (Оборотная сторона заключается в том, что чрезмерно мелкомодульная асинхронность может нанести ущерб производительности, потому что с асинхронными операциями связаны определенные накладные расходы).

При подготовке видео использована книга-бестселлер "C# 7.0 in a Nutshell: The Definitive Reference" от авторов Джозеф Албахари и Бен Албахари https://www.amazon.com/C-7-0-Nutshell-D … 491987650/ К данной книге прилагается утилита LINQPad, которая сопровождается одним из авторов книги в качестве инструмента подготовки кода https://www.linqpad.net/ В ней можно бесплатно загрузить и апробировать исходные коды программ, продемонстрированные в данном видео.