Слайд 2Однопоточность
Система в одном потоке работает со всеми задачами, выполняя их поочерёдно.
Слайд 3Многопоточность
В этом случае речь о нескольких потоках, в которых выполнение задач идет
одновременно и независимо друг от друга.
Пример такого концепта — одновременная разработка веб- и мобильного приложений и серверной части, при условии соблюдения архитектурных «контрактов».
Использование нескольких потоков выполнения — один из способов обеспечить возможность реагирования приложения на действия пользователя при одновременном использовании процессора для выполнения задач между появлением или даже во время появления событий пользователя.
Слайд 4Асинхронность
Характеристики асинхронного кода:
обрабатывает больше запросов сервера, предоставляя потокам возможность обрабатывать больше запросов
во время ожидания результата от запросов ввода-вывода;
делает пользовательский интерфейс быстрым, выделяя потоки для обработки действий в пользовательском интерфейсе во время ожидания запросов ввода-вывода, передавая затратные по времени операции другим ядрам ЦП.
Слайд 5Многопоточность VS Асинхронность
Многопоточность — параллельное выполнение, асинхронность — логическая оптимизация выполнения, которая
может работать и в одном, и во многих потоках.
Слайд 6Проблемы многопоточности
Многозадачность
Вытесняющая
Кооперативная
Проблемы планирования задач
Переключение контекста
Приоритеты
Общая память
Условия гонок (race condition)
Взаимная блокировка (deadlock)
Слайд 7Асинхронный код в .NET
В .NET-фреймворке исторически сложилось несколько более старых паттернов организации
асинхронного кода:
APM (IAsyncResult, они же коллбеки) (.NET 1.0).
EAP — события, этот паттерн все видели в WinForms (.NET 2.0).
TAP (Task Asynchronous Pattern) — класс Task и его экосистема (.NET 4.0).
Слайд 8Класс System.Threading.Thread
Класс Thread является самым элементарным из всех типов в пространстве имен
System.Threading. Он представляет объектно-ориентированную оболочку вокруг заданного пути выполнения внутри отдельного домена приложения. В этом классе определено несколько методов (статических и уровня экземпляра), которые позволяют создавать новые потоки внутри текущего домена приложения, а также приостанавливать, останавливать и уничтожать указанный поток.
Слайд 11Thread. Свойство Priority
Lowest
BelowNormal
Normal
AboveNormal
Highest
Слайд 12Thread. Конструкторы
Thread(ParameterizedThreadStart) - Инициализирует новый экземпляр класса Thread, при этом указывается делегат,
позволяющий объекту быть переданным в поток при запуске потока.
Thread(ThreadStart) - Инициализация нового экземпляра класса Thread.
Thread(ParameterizedThreadStart, Int32) - Инициализирует новый экземпляр класса Thread, при этом указывается делегат, позволяющий объекту быть переданным в поток при запуске потока с указанием максимального размера стека для потока.
Thread(ThreadStart, Int32) - Инициализирует новый экземпляр класса Thread, указывая максимальный размер стека для потока.
Слайд 15Thread. Запуск потока с параметрами
Слайд 16Оператор lock
Оператор lock определяет блок кода, внутри которого весь код блокируется и
становится недоступным для других потоков до завершения работы текущего потока.
Избегайте использования следующих объектов в качестве объектов блокировки:
this, так как он может использоваться вызывающими объектами как блокировка;
экземпляров Type, так как их может получать оператор typeof или отражение;
строковых экземпляров, включая строковые литералы, так как они могут быть интернированы.
Слайд 17Monitor
Наряду с оператором lock для синхронизации потоков мы можем использовать мониторы, представленные
классом System.Threading.Monitor. Фактически конструкция оператора lock инкапсулирует в себе синтаксис использования мониторов.
Слайд 18Semaphore
Семафоры позволяют ограничить доступ определенным количеством объектов.
Его конструктор принимает два параметра: первый
указывает, какому числу объектов изначально будет доступен семафор, а второй параметр указывает, какой максимальное число объектов будет использовать данный семафор.
Потоки вводят семафор, вызывая метод WaitOne(), который наследуется от класса WaitHandle, и освобождает семафор, вызывая метод Release().
Счетчик для семафора уменьшается каждый раз, когда поток входит в семафор, и увеличивается, когда поток освобождает семафор. Если значение счетчика равно нулю, последующие запросы блокируются до освобождения семафора другими потоками. Когда семафор освобожден всеми потоками, счетчик будет иметь максимальное значение, указанное при создании семафора.
Нет гарантированного порядка, например FIFO или LIFO, в котором заблокированные потоки вводят семафор.
Локальный семафор существует только в пределах процесса. Его может использовать любой поток в вашем процессе, имеющий ссылку на локальный объект Semaphore. Каждый объект Semaphore является отдельным локальным семафором.
Слайд 19Mutex
Mutex является классом-оболочкой над соответствующим объектом ОС Windows "мьютекс".
Когда двум или более
потокам требуется одновременный доступ к общему ресурсу, системе необходим механизм синхронизации, гарантирующий, что ресурс будет использоваться только одним потоком в каждый момент времени. Mutex — это примитив синхронизации, предоставляющий эксклюзивный доступ к общему ресурсу только одному потоку. Если поток получает мьютекс, второй поток, желающий получить этот мьютекс, приостанавливается до тех пор, пока первый поток не освободит мьютекс.
Класс Mutex обеспечивает идентификацию потоков, поэтому мьютекс может быть освобожден только потоком, который его получил. В отличие от этого, класс Semaphore не применяет удостоверение потока. Мьютекс также может передаваться через границы домена приложения.
Основную работу по синхронизации выполняют методы WaitOne() и ReleaseMutex().
Слайд 20Interlocked
Предоставляет атомарные операции для переменных, общедоступных нескольким потокам.
public void AddOne()
{
int newVal
= Interlocked.Increment(ref intVal);
}
Слайд 21ThreadPool
ThreadPool - Предоставляет пул потоков, который можно использовать для выполнения задач, отправки
рабочих элементов, обработки асинхронного ввода-вывода, ожидания от имени других потоков и обработки таймеров.
Многие приложения создают потоки, которые тратят много времени на спящий режим, ожидая возникновения события. Другие потоки могут войти в спящее состояние только для периодического опроса на наличие изменений или сведений о состоянии обновления. Пул потоков позволяет более эффективно использовать потоки, предоставляя приложению пул рабочих потоков, управляемых системой
Слайд 23Параллельное программирование и библиотека TPL
В эпоху многоядерных машин, которые позволяют параллельно выполнять
сразу несколько процессов, стандартных средств работы с потоками в .NET уже оказалось недостаточно. Поэтому во фреймворк .NET была добавлена библиотека параллельных задач TPL (Task Parallel Library), основной функционал которой располагается в пространстве имен System.Threading.Tasks. Данная библиотека позволяет распараллелить задачи и выполнять их сразу на нескольких процессорах, если на целевом компьютере имеется несколько ядер. Кроме того, упрощается сама работа по созданию новых потоков. Поэтому начиная с .NET 4.0. рекомендуется использовать именно TPL и ее классы для создания многопоточных приложений, хотя стандартные средства и класс Thread по-прежнему находят широкое применение.
Слайд 24Task-based asynchronous pattern (TAP)
Слайд 25async/await
Ключевыми для работы с асинхронными вызовами в C# являются два ключевых слова: async и await,
цель которых – упростить написание асинхронного кода. Они используются вместе для создания асинхронного метода.
Асинхонный метод обладает следующими признаками:
В заголовке метода используется модификатор async
Метод содержит одно или несколько выражений await
В качестве возвращаемого типа используется один из следующих:
void
Task
Task
ValueTask
Слайд 26Как создать и запустить задачу?
Фабрики запущенных задач. Run — более легкая версия метода StartNew
с установленными дополнительными параметрами по умолчанию. Возвращает созданную и запущенную задачу. Самый популярный способ запуска задач. Оба метода вызывают скрытый от нас Task.InternalStartNew. Возвращают объект Task.
Фабрики завершенных задач. Иногда нужно вернуть результат задачи без необходимости создавать асинхронную операцию. Это может пригодиться в случае подмены результата операции на заглушку при юнит-тестировании или при возврате заранее известного/рассчитанного результата.
Конструктор. Создает незапущенную задачу, которую вы можете далее запустить. Я не рекомендую использовать этот способ. Старайтесь использовать фабрики, если это возможно, чтобы не писать дополнительную логику по запуску.
Фабрики-таскофикаторы. Помогают либо произвести миграцию с других асинхронных моделей в TAP, либо обернуть логику ожидания результата в вашем классе в TAP. Например, FromAsync принимает методы паттерна APM в качестве аргументов и возвращает Task, который оборачивает более ранний паттерн в новый.
Слайд 27Отмена асинхронных операций
Для отмены асинхронных операций используются классы CancellationToken и CancellationTokenSource.
CancellationToken содержит информацию о том,
надо ли отменять асинхронную задачу. Асинхронная задача, в которую передается объект CancellationToken, периодически проверяет состояние этого объекта. Если его свойство IsCancellationRequested равно true, то задача должна остановить все свои операции.
Для создания объекта CancellationToken применяется объект CancellationTokenSource. Кроме того, при вызове у CancellationTokenSource метода Cancel() у объекта CancellationToken свойство IsCancellationRequested будет установлено в true.
Слайд 29Как следить за прогрессом выполнения?
TAP содержит специальный интерфейс для использования в своих асинхронных классах —
IProgress, где T — тип, содержащий информацию о прогрессе, например int. Согласно конвенциям, IProgress может передаваться как последние аргументы в метод вместе с CancellationToken. В случае если вы хотите передать только что-то из них, в паттерне существуют значения по умолчанию: для IProgress принято передавать null, а для CancellationToken — CancellationToken.None, так как это структура.
Слайд 31Task.WaitAll(tasks) и Task.WaitAny(tasks)
Слайд 32Как извлечь результат из задачи?
До появления await извлекать результат из задач можно было такими блокирующими
способами:
t.Result(); — возврат результата / выброс исключения AggregateException.
t.Wait(); — ожидание выполнения задачи, выброс исключения AggregateException.
t.GetAwaiter().GetResult(); — возврат результата / выброс оригинального исключения — служебный метод компилятора, поэтому использовать его не рекомендуется. Используется механизмом async/await.
После появления async/await рекомендованной техникой стал оператор await, производящий неблокирующее ожидание. То есть если await добрался до незавершенной задачи, выполнение кода в потоке будет прервано и продолжится только с завершением задачи.
await t; — возврат результата / выброс оригинального исключения.
Следует заметить, что для t.GetAwaiter().GetResult(); и await будет выброшено только первое исключение, аналогично манере поведения обычного синхронного кода.
Слайд 33Как запустить задачу и
ожидать ее результат?
Слайд 34Как запустить задачу и ожидать ее результат?
Слайд 38Домашка
Чем отличается SemaphoreSlim от Semaphore? Когда какой выбрать?
Прочитать статью про асинхронное программирование
(ссылка в «Что почитать»)
Задание: С использованием Thread-ов написать приложение, реализующее патерн Publisher/Subscriber. Необходимо реализовать 2 класса Publisher, который генерирует случайные целые числа и складывает их в очередь. Класс Subscriber, который опрашивает очередь и вычитывает из нее числа и выводит их в консоль. Очередь должна быть потокобезопасная. В программе запуска может быть несколько экземпляров типа Publisher, которые одновременно генерируют случайные числа и складывают их в очередь. Должен быть только один объект типа Subscriber получающий данные из очереди.