29
Многопоточное программирование на C#, путевые заметки Дмитрий Литичевский ByndyuSoft 11-я конференция .NET разработчиков 31 октября 2015 dotnetconf.ru

Многопоточное программирование на C#, путевые заметки

Embed Size (px)

Citation preview

Page 1: Многопоточное программирование на C#, путевые заметки

Многопоточное

программирование на C#,

путевые заметки

Дмитрий Литичевский

ByndyuSoft

11-я конференция .NET разработчиков

31 октября 2015

dotnetconf.ru

Page 2: Многопоточное программирование на C#, путевые заметки

2

О себе Team lead в компании ByndyuSoft

Аспирант ЧелГУ, дискретная математика и

математическая кибернетика

Любитель вселенной Warhammer 40000

В МРАЧНОЙ ТЬМЕ ДАЛЁКОГО БУДУЩЕГО ЕСТЬ ТОЛЬКО ВОЙНА...

Page 3: Многопоточное программирование на C#, путевые заметки

3

Прежде чем начать

Зачем нам все это?

Хотим эффективно использовать имеющиеся ресурсы.

Когда это сработает?

Обрабатывается поток ресурсоемких слабосвязанных

друг с другом задач

Page 4: Многопоточное программирование на C#, путевые заметки

4

Асинхронная обработка

Одним из способов ускорить приложения являетсяасинхронный запуск длительных операций, мызапускаем ее и не ждем завершения

Возможные примеры:

1. Cетевые запросы;

2. Доступ к диску;

3. Сложные вычисления.

l

Основное различие заключается в том, в каком потокевыполняется запущенная длительная операция.

Page 5: Многопоточное программирование на C#, путевые заметки

5

Что предлагает нам C#

Event-based Asynchronous Pattern (EAP) private void DownloadContentFromUrl(Uri uri){

var client = new WebClient();client.DownloadStringCompleted += OnDownloadStringCompleted;client.DownloadStringAsync(uri);

}

Интерфейс IAsyncResultprivate void LookupHostName(){

Dns.BeginGetHostAddresses("dotnetconf.ru", OnNameResolved, null);}private void OnNameResolved(IAsyncResult ar){

var addresses = Dns.EndGetHostAddresses(ar);}

Task-based Asynchronous Pattern (TAP)

Page 6: Многопоточное программирование на C#, путевые заметки

6

Договор с TAP

Не допустимы ref и out параметры методов.

Метод должен возвращать значение типа Task<T>, Task илиvoid в зависимости от возвращаемого значения синхронногометода.

Исключение из-за ошибки в параметрах вызова метода можновозбуждать непосредственно (используя оператор throw),остальные исключения следует помещать в объект Task.

Единый API для работы с асинхронными операциями, чтопозволяет реализовать на уровне компилятора такие средствакак async/await.

Page 7: Многопоточное программирование на C#, путевые заметки

7

Примеры использования TAP

var x = await Task.Run(() => SolveSystem(a, b));

Task t = Task.Factory.StartNew(() => SolveSystem(a, b),cts.Token,TaskCreationOptions.LongRunning,TaskScheduler.Current);

private async Task DownloadPageAsync(string uri){

using (var client = new HttpClient())using (var response = await client.GetAsync(uri))using (var content = response.Content){

var result = await content.ReadAsStringAsync();// Processing

}}

Page 8: Многопоточное программирование на C#, путевые заметки

8

TAP, обработка ошибок

Методы возвращают Task илиTask<T>, ожидаем простойтаск используя await.

try{

var pageContent = await webClient.DownloadStringTaskAsync(uri);// Processing

}catch (Exception ex){

// Handling}

Исключение возбуждается там, где находится операторawait, перехватывается, все красиво и здорово.

Page 9: Многопоточное программирование на C#, путевые заметки

9

TAP, обработка ошибок

Методы возвращают Task илиTask<T>, ожидаемрезультат используя методы Wait, WaitAll, WaitAny

try{

var task = webClient.DownloadStringTaskAsync(uri);task.Wait();// Processing

}catch (AggregateException ex){

foreach (var inner in ex.InnerExceptions) {/*Handling*/ }}

AggregateException возбуждается в месте вызоваWait, WaitAll, WaitAny, возникшие исключениянаходятся в свойстве InnerExceptions.

Page 10: Многопоточное программирование на C#, путевые заметки

10

TAP, обработка ошибокМетоды оформлены по стандарту, ожидаем составной таск

var allTask = Task.WhenAll(tasks);try{

await allTask;}catch{

foreach (var ex in allTask.Exception.InnerExceptions){

// Handling}

}

При выполнении тасков может возникать несколько исключений, но в операторе await возбуждается одно из них. Все исключения могутбыть найдены в коллекции InnerExceptions объекта классаAggregateException, лежащего в свойстве Exceptionожидаемого таска.

Page 11: Многопоточное программирование на C#, путевые заметки

11

TAP, обработка ошибокАсинхронный метод возвращает Task или void, мы не хотим или не можем

дождаться его завершения. Тогда мы можем:

1. Реализовать обработку ошибок внутри самой операции;

2. Если метод все же возвращает таск, то добавить к нему Continuation

var task = DownloadPageAsync("http://dotnetconf.ru/");task.ContinueWith(t => t.Exception.Handle(e =>

{// Handlingreturn true;

}),

TaskContinuationOptions.OnlyOnFaulted);

private async Task DownloadPageAsync(string uri){

using (var webclient = new WebClient()){

var content = await webclient.DownloadStringTaskAsync(uri);// Processing

}}

Page 12: Многопоточное программирование на C#, путевые заметки

12

TAP, отмена операцииИспользуется классы CancellationToken и CancellationTokenSource

Способы прерывания:

1. По прошествии определенного времени с помощью CancelAfter

2. В ручную с помощью Cancell

using (var cts = new CancellationTokenSource()){

cts.CancelAfter(TimeSpan.FromMilliseconds(100));try{

var task = DownloadPageAsync("http://dotnetconf.ru/", cts.Token);task.Wait(cts.Token);

}catch (OperationCanceledException){

// Handling}catch (AggregateException){

// Handling}

}

Page 13: Многопоточное программирование на C#, путевые заметки

13

TAP, отмена операцииОбработка сводится к периодической проверке вызываемым кодом

свойства IsCancellationRequested и вызову методаThrowIfCancellationRequested объекта CancellationToken.

Task.Run(() =>{

for (var i = 0; i < n; i++){

// Calculationstoken.ThrowIfCancellationRequested();

}

}, token)

.Wait();

Если требуется освобождение ресурсов, то мы можем периодическипроверять флаг IsCancellationRequested.

Page 14: Многопоточное программирование на C#, путевые заметки

14

Типы многопоточная обработки

Организацию многопоточной обработки данных вприложениях можно разделить на два типа:

1. Задачи заранее известны, организовывать их обработкубудем с использованием библиотек TPL или PLINQ.

2. Задачи генерируются в процессе обработки, модельproducer and consumer будем реализовывать припомощи класса ThreadPool или библиотеки Rx.

Данная классификация является условной призвана помочьавтору структурировать остальную часть доклада.

Page 15: Многопоточное программирование на C#, путевые заметки

15

TPL, теория

TPL содержит класс Parallel, возможности которого аналогичныбиблиоке OpenMP для С++. Для распараллеливания циклов в классеобъявлены три перегруженных метода:

1. For, организует распаралеленный цикл for, в качестве одного изаргументов передается тело цикла.

2. ForEach, организует распаралеленную обработку коллекций,реализующих интерефейс IEnumerable<T>, в качестве одного изаргументов передается тело цикла.

3. Invoke, организует параллельный вызов экземпляров Action,переданных в качестве параметров.

Ошибки, возникшие и не перехваченные в обрабатывающих потоках,помещаются в экземпляр AggregateException. Вызвавшийметоды поток блокируется до завершения обработки.

Page 16: Многопоточное программирование на C#, путевые заметки

16

TPL, примеры

Parallel.ForEach(orders,

new ParallelOptions {MaxDegreeOfParallelism = threadsCount},

UpdateOrder);

Parallel.Invoke(

() => DeleteSubjects(orderId),

() => DeleteDocuments(orderId),

() => DeleteSuppliers(orderId)

);

Page 17: Многопоточное программирование на C#, путевые заметки

17

TPL, нюансыИспользование экземпляра ParallelOptions позволяет более полно

контролировать выполняемые операции:

1. MaxDegreeOfParallelism, регулирует количество потоков, которые будут использованы для обработки.

2. CancellationToken, токен, используемый для отменызапускаемой операции.

3. TaskScheduler, управляет запуском потоков обработки задач.

Использование класса Partitioner позволяет управлять разбиением задачмежду потоками и их балансировкой. TPL может динамически распределятьнагрузку между потоками или же может распределить их некоторымобразом перед стартом выполнения расчетов и не менять по ходуобработки, это настраивается с помощью Partitioner.Create.

var tasks = Enumerable.Range(1, 100).ToArray();Parallel.ForEach(Partitioner.Create(tasks, true), ProcessItem);

Page 18: Многопоточное программирование на C#, путевые заметки

18

PLINQ, теория

PLINQ является реализацией LINQ, распеределенно выполняющейзапросы. Для этого были добавлены классыParallelEnumerable, ParallelQuery, ParallelQuery<T>, атакже нескольких других. Не все операторы LINQ реализованы вPLINQ.

Этапы выполнения запроса:

1. Выбор модели выполнения (параллельно или последовательно),будем считать, что запрос будет выполняться параллельно.

2. Распределение задач между потоками.

3. Запуск выполнения запроса;

4. Слияние результатов работы отдельных потоков в вызывающемпотоке;

5. Итерация по результатам запроса.

Ошибки, возникшие и не перехваченные в обрабатывающих потоках,помещаются в экземпляр AggregateException.

Page 19: Многопоточное программирование на C#, путевые заметки

19

PLINQ, пример

var tradeItemsForUpdate = tradeItems.AsParallel().WithDegreeOfParallelism(_threadsCount).Where(UpdateNeeded).ToArray();

var range = Enumerable.Range(0, 100000000).ToArray();var partitioner = Partitioner.Create(range, true);var query = partitioner.AsParallel().Select(Calc);

Для написания запросов с использованием PLINQ достаточно вызватьметод разширения ParallelEnumerable.AsParallel послечего запрос будет строиться с использованием методов PLINQ.

Page 20: Многопоточное программирование на C#, путевые заметки

20

PLINQ, нюансы

В случае 100% уверенности в том, что распараллеливание запросапринесет прирост в скорости, можно заставить среду исполнять егопараллельно, вызвав метода WithExecutionMode, вызванного c параметром ParallelExecutionMode.ForceParallelism.

var ordersForUpdate = orders.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism).Where(UpdateNeeded).ToArray();

Разбиение задач между потоками настраивается. Для этогоиспользуется уже описанный ранее класс Partitioner и егометод Create. С его помощью можно включить динамическуюбалансировку задач, отключенную по умолчанию для массивов и коллекций, реализующих Ilist. Если описанного недостаточно, томожно реализовать свой Partitioner согласно требованиям TPL.

Page 21: Многопоточное программирование на C#, путевые заметки

21

PLINQ, нюансыВ отличии от первых двух пунктов цепи выполнения запроса, существует

гораздо больше возможностей влияния на его выполнение. Допустимоследующее:

1. Управлять числом обрабатывающих потоков с помощью методаWithDegreeOfParallelism.

2. Управлять досрочным прекращением операций с помощью методаWithCancellation, передав в качестве параметра экземплярCancellationToken.

3. Управлять учетом порядка записей исходной коллекции при генерациирезультатов с помощью методов AsOrdered/AsUnordered.

var ordersForUpdate = orders.AsParallel().AsOrdered().WithDegreeOfParallelism(threadsCount).WithCancellation(cts.Token).Where(UpdateNeeded).ToArray();

Page 22: Многопоточное программирование на C#, путевые заметки

22

PLINQ, нюансыДля обработки результатов запросов из примеров необходимо

слить вместе результаты отдельных потоков и отдать ихвызвавшему. Это настраивается при помощи методаWithMergeOptions, доступны следующие варианты:

1. FullyBuffered, результаты полностью буферизуются.

2. AutoBuffered, результаты частично буферизуются.

3. NotBuffered, результаты не буферизуются.

var ordersForUpdate = orders.AsParallel().WithMergeOptions(ParallelMergeOptions.FullyBuffered).Where(UpdateNeeded);

foreach (var order in ordersForUpdate)

UpdateOrder(order);

Page 23: Многопоточное программирование на C#, путевые заметки

23

PLINQ, нюансы

Если обработка результатов запроса может быть выполненапараллельно и ее порядок не важен, то можновоспользоваться методом ForAll, передав в него тело цикла, сэкономив на слиянии промежуточных результатов.

orders.AsParallel().ForAll(x =>

{if(UpdateNeeded(x))

UpdateOrder(x);});

Page 24: Многопоточное программирование на C#, путевые заметки

24

Использование ThreadPool

Простейший вариант параллельной обработки — это использованиеметода QueueUserWorkItem класса ThreadPool.

while (true){

var message = GetMessage(queueName);

if (message != null)ThreadPool.QueueUserWorkItem(DispatchMessage, message);

elseThread.Sleep(30000);

}

Page 25: Многопоточное программирование на C#, путевые заметки

25

Использование ThreadPool

Для ограничения числа отправляемых в ThreadPool задач, например, причтении из очереди, можно использовать Semaphore.

_semaphore.WaitOne();ThreadPool.QueueUserWorkItem(DispatchMessage, message);

private void DispatchMessage(object state){

var message = (MessageInfo)state;try{

OnMessageReceive(this, message);}finally{

_semaphore.Release();}

}

Page 26: Многопоточное программирование на C#, путевые заметки

26

Использование Rx

Состоит из интерфейсов Iobservable<T> и IObserver<T>, включенных в .NET начиная с версии 4 и nuget пакета Rx-Main.

public interface IObservable<T>{

IDisposable Subscribe(IObserver<T> observer);}public interface IObserver<T>{

void OnNext(T value);void OnCompleted();void OnException(Exception error);

}

Rx предлагает другую модель обработки данных, push вместо pull, о новыхсобытиях нас будет уведомлять сама библиотека.

Page 27: Многопоточное программирование на C#, путевые заметки

27

Использование Rx

Библиотека позволяет настраивать, в каких потоках будут выполняться методыObserver'а. Для этих целей служат методы SubscribeOn и ObserveOnкласса Observable.

_customerService.GetCustomers()

.SubscribeOn(Scheduler.TaskPool)

.Subscribe(Customers.Add);

Метод ObserveOn определяет какой Scheduler'е будет использоваться длявызовов методов Observer'а. Метод SubscribeOn определяет где будетпорождаться и обрабатываться уведомления для Observer'ов. Интересныследующие Scheduler'ы:

1. Scheduler.NewThread, обработка будет запущена в отдельном потоке.

2. Scheduler.ThreadPool, обработка будет запущена в ThreadPool.

3. Scheduler.TaskPool, обработка будет запущена в TaskPool.

Page 28: Многопоточное программирование на C#, путевые заметки

28

Литература и примерыЛитература:1. https://msdn.microsoft.com/ru-ru/library/dd997415(v=vs.110).aspx

2. https://msdn.microsoft.com/ru-ru/library/hh524395.aspx

3. https://msdn.microsoft.com/en-us/library/jj155759.aspx

4. http://habrahabr.ru/post/168669/

5. https://msdn.microsoft.com/en-us/library/dd997425(v=vs.110).aspx

6. https://msdn.microsoft.com/en-us/library/dd997411.aspx

7. http://stackoverflow.com/questions/15773008/reactive-framework-as-message-queue-using-blockingcollection

8. http://www.introtorx.com/content/v1.0.10621.0/15_SchedulingAndThreading.html

9. Асинхронное программирование в C# 5.0, Алекс Дэвис

10. Concurrency in C# Cookbook, Stephen Cleary

11. C# in depth, Jon Skeet

Примеры: github

Page 29: Многопоточное программирование на C#, путевые заметки

29

Спасибо за внимание

Литичевский Дмитрий

ByndyuSoft

[email protected]

vk