Параллелизм, многопоточность, асинхронность: разница и примеры применения (.NET, C#)
Давайте разберёмся, сколько программных моделей используют C#-разработчики и в чём их отличия. Особенности каждой модели рассмотрим подробно.
26К открытий28К показов
Тимур Гайфулин
Руководитель группы разработки digital-интегратора DD Planet
Многие начинающие специалисты путают многопоточное, асинхронное и параллельное программирование. На первый взгляд, может показаться, что это одно и то же — но нет. Давайте разберёмся, сколько программных моделей используют C#-разработчики и в чём их отличия. Материал подготовлен совместно с Алексеем Гришиным, ведущим разработчиком DD Planet.
Существует несколько концепций: синхронное/асинхронное программирование и однопоточные/многопоточные приложения. Причём первая программная модель может работать в однопоточной или многопоточной среде. То есть приложение может быть: синхронным однопоточным, синхронным многопоточным и асинхронным многопоточным.
Отдельной концепцией считается параллелизм, который является подмножеством многопоточного типа приложений. Рассмотрим особенности каждой программной модели подробнее.
Синхронная модель
Потоку назначается одна задача, и начинается её выполнение. Заняться следующей задачей можно только тогда, когда завершится выполнение первой. Эта модель не предполагает приостановку одной задачи, чтобы выполнить другую.
Однопоточность
Система в одном потоке работает со всеми задачами, выполняя их поочерёдно.
Однопоточная синхронная система[/caption]
Многопоточность
В этом случае речь о нескольких потоках, в которых выполнение задач идет одновременно и независимо друг от друга.
Многопоточная синхронная система[/caption]
Пример такого концепта — одновременная разработка веб- и мобильного приложений и серверной части, при условии соблюдения архитектурных «контрактов».
Использование нескольких потоков выполнения — один из способов обеспечить возможность реагирования приложения на действия пользователя при одновременном использовании процессора для выполнения задач между появлением или даже во время появления событий пользователя.
Асинхронность
Характеристики асинхронного кода:
- обрабатывает больше запросов сервера, предоставляя потокам возможность обрабатывать больше запросов во время ожидания результата от запросов ввода-вывода;
- делает пользовательский интерфейс быстрым, выделяя потоки для обработки действий в пользовательском интерфейсе во время ожидания запросов ввода-вывода, передавая затратные по времени операции другим ядрам ЦП.
Если у системы много потоков, то их асинхронная работа выглядит примерно так:
Многопоточная асинхронная система[/caption]
Конструкция async/await
Для работы с асинхронными вызовами в C# необходимы два ключевых слова:
- async — используется в заголовке метода;
- await — вызывающий метод содержит одно или несколько таких выражений.
Они используются вместе для создания асинхронного метода. У асинхронных методов могут быть следующие типы возвращаемых значений:
- Task для асинхронного метода, который выполняет операцию, но не возвращает значение;
Task<TResult>
для асинхронного метода, возвращающего значение;- void для обработчика событий;
- начиная с версии 7.0 в языке C# поддерживаются любые типы с доступным методом GetAwaiter;
- начиная с версии 8.0 в языке C# поддерживается интерфейс
IAsyncEnumerable<T>
для асинхронного метода, который возвращает асинхронный поток.
Сама конструкция async/await появилась в C# 5.0 с выходом .NET Framework 4.5 и отчасти представляет собой синтаксический сахар. Механизм async/await не имеет реализации в CLR и разворачивается компилятором в сложную конструкцию на IL. Но эта конструкция — не сахар вокруг тасок, а отдельный механизм, использующий класс Task для переноса состояния исполняемой части кода.
Пример асинхронного метода:
Результат асинхронного вычисления факториала[/caption]
Этот пример приведён лишь для наглядности, особого смысла делать логику вычисления факториала асинхронной нет. Опять же, для имитации долгой работы мы использовали задержку на 8 секунд с помощью методы Thread.Sleep(). Цель была показать: асинхронная задача, которая может выполняться долгое время, не блокирует основной поток — в этом случае метод Main(), и мы можем вводить и обрабатывать данные, продолжая работу с ним.
Параллелизм
Эта программная модель подразумевает, что задача разбивается на несколько независимых подзадач, которые можно выполнить параллельно, а затем объединить результаты. Примером такой задачи может быть Parallel LINQ:
Обзор архитектуры параллельного программирования в .NET[/caption]
Еще один пример — вычисление среднего значения двумерного массива, когда каждый отдельный поток может подсчитать сумму своей строки, а потом объединить результат и вычислить среднее.
Однако не стоит забывать, что не все задачи поддаются распараллеливанию. Например, описанная выше задача по вычислению факториала, в которой на каждом последующем этапе нужен результат предыдущего.
Какую программную модель выбрать?
Перечисленные программные модели должны применяться в зависимости от задач. Их можно использовать как отдельно во всём приложении, так и сочетать между собой. Главное, чтобы приложение было максимально эффективным и удовлетворяло требования пользователя.
Если речь идет о сложных многопользовательских приложениях, то стремиться стоит к использованию асинхронной модели, так как важна интерактивность и отзывчивость интерфейса. Взаимодействие с пользователем в активном режиме всегда должно быть максимально эффективным, даже если в фоновом режиме в то же время выполняются другие задачи. Издержки асинхронности, например, на переключение исполняемого контекста, в таком случае нивелируются за счет общей эффективности приложения.
В разработке простых приложений, к примеру, парсера документа, необходимости в асинхронности, или даже многопоточности, может и не быть.
26К открытий28К показов