2. 2
О себе
Team lead в компании ByndyuSoft
Аспирант ЧелГУ, дискретная математика и
математическая кибернетика
Любитель вселенной Warhammer 40000
В МРАЧНОЙ ТЬМЕ ДАЛЁКОГО
БУДУЩЕГО ЕСТЬ ТОЛЬКО
ВОЙНА...
3. 3
Прежде чем начать
Зачем нам все это?
Хотим эффективно использовать
имеющиеся ресурсы.
Когда это сработает?
Обрабатывается поток
ресурсоемких слабосвязанных
друг с другом задач
4. 4
Асинхронная обработка
Одним из способов ускорить приложения является
асинхронный запуск длительных операций, мы
запускаем ее и не ждем завершения
Возможные примеры:
1. Cетевые запросы;
2. Доступ к диску;
3. Сложные вычисления.
l
Основное различие заключается в том, в каком потоке
выполняется запущенная длительная операция.
5. 5
Что предлагает нам C#
Event-based Asynchronous Pattern (EAP)
private void DownloadContentFromUrl(Uri uri)
{
var client = new WebClient();
client.DownloadStringCompleted += OnDownloadStringCompleted;
client.DownloadStringAsync(uri);
}
Интерфейс IAsyncResult
private void LookupHostName()
{
Dns.BeginGetHostAddresses("dotnetconf.ru", OnNameResolved, null);
}
private void OnNameResolved(IAsyncResult ar)
{
var addresses = Dns.EndGetHostAddresses(ar);
}
Task-based Asynchronous Pattern (TAP)
6. 6
Договор с TAP
Не допустимы ref и out параметры методов.
Метод должен возвращать значение типа Task<T>, Task или
void в зависимости от возвращаемого значения синхронного
метода.
Исключение из-за ошибки в параметрах вызова метода можно
возбуждать непосредственно (используя оператор throw),
остальные исключения следует помещать в объект Task.
Единый API для работы с асинхронными операциями, что
позволяет реализовать на уровне компилятора такие средства
как async/await.
7. 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
}
}
8. 8
TAP, обработка ошибок
Методы возвращают Task илиTask<T>, ожидаем простой
таск используя await.
try
{
var pageContent = await webClient.DownloadStringTaskAsync(uri);
// Processing
}
catch (Exception ex)
{
// Handling
}
Исключение возбуждается там, где находится оператор
await, перехватывается, все красиво и здорово.
9. 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.
10. 10
TAP, обработка ошибок
Методы оформлены по стандарту, ожидаем составной таск
var allTask = Task.WhenAll(tasks);
try
{
await allTask;
}
catch
{
foreach (var ex in allTask.Exception.InnerExceptions)
{
// Handling
}
}
При выполнении тасков может возникать несколько исключений, но в
операторе await возбуждается одно из них. Все исключения могут
быть найдены в коллекции InnerExceptions объекта класса
AggregateException, лежащего в свойстве Exception
ожидаемого таска.
11. 11
TAP, обработка ошибок
Асинхронный метод возвращает Task или void, мы не хотим или не можем
дождаться его завершения. Тогда мы можем:
1. Реализовать обработку ошибок внутри самой операции;
2. Если метод все же возвращает таск, то добавить к нему Continuation
var task = DownloadPageAsync("http://dotnetconf.ru/");
task.ContinueWith(t => t.Exception.Handle(e =>
{
// Handling
return true;
}),
TaskContinuationOptions.OnlyOnFaulted);
private async Task DownloadPageAsync(string uri)
{
using (var webclient = new WebClient())
{
var content = await webclient.DownloadStringTaskAsync(uri);
// Processing
}
}
12. 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
}
}
13. 13
TAP, отмена операции
Обработка сводится к периодической проверке вызываемым кодом
свойства IsCancellationRequested и вызову метода
ThrowIfCancellationRequested объекта CancellationToken.
Task.Run(() =>
{
for (var i = 0; i < n; i++)
{
// Calculations
token.ThrowIfCancellationRequested();
}
}, token)
.Wait();
Если требуется освобождение ресурсов, то мы можем периодически
проверять флаг IsCancellationRequested.
14. 14
Типы многопоточная обработки
Организацию многопоточной обработки данных в
приложениях можно разделить на два типа:
1. Задачи заранее известны, организовывать их обработку
будем с использованием библиотек TPL или PLINQ.
2. Задачи генерируются в процессе обработки, модель
producer and consumer будем реализовывать при
помощи класса ThreadPool или библиотеки Rx.
Данная классификация является условной призвана помочь
автору структурировать остальную часть доклада.
15. 15
TPL, теория
TPL содержит класс Parallel, возможности которого аналогичны
библиоке OpenMP для С++. Для распараллеливания циклов в классе
объявлены три перегруженных метода:
1. For, организует распаралеленный цикл for, в качестве одного из
аргументов передается тело цикла.
2. ForEach, организует распаралеленную обработку коллекций,
реализующих интерефейс IEnumerable<T>, в качестве одного из
аргументов передается тело цикла.
3. Invoke, организует параллельный вызов экземпляров Action,
переданных в качестве параметров.
Ошибки, возникшие и не перехваченные в обрабатывающих потоках,
помещаются в экземпляр AggregateException. Вызвавший
методы поток блокируется до завершения обработки.
17. 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);
18. 18
PLINQ, теория
PLINQ является реализацией LINQ, распеределенно выполняющей
запросы. Для этого были добавлены классы
ParallelEnumerable, ParallelQuery, ParallelQuery<T>, а
также нескольких других. Не все операторы LINQ реализованы в
PLINQ.
Этапы выполнения запроса:
1. Выбор модели выполнения (параллельно или последовательно),
будем считать, что запрос будет выполняться параллельно.
2. Распределение задач между потоками.
3. Запуск выполнения запроса;
4. Слияние результатов работы отдельных потоков в вызывающем
потоке;
5. Итерация по результатам запроса.
Ошибки, возникшие и не перехваченные в обрабатывающих потоках,
помещаются в экземпляр AggregateException.
19. 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.
20. 20
PLINQ, нюансы
В случае 100% уверенности в том, что распараллеливание запроса
принесет прирост в скорости, можно заставить среду исполнять его
параллельно, вызвав метода WithExecutionMode, вызванного c
параметром ParallelExecutionMode.ForceParallelism.
var ordersForUpdate = orders.AsParallel()
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.Where(UpdateNeeded)
.ToArray();
Разбиение задач между потоками настраивается. Для этого
используется уже описанный ранее класс Partitioner и его
метод Create. С его помощью можно включить динамическую
балансировку задач, отключенную по умолчанию для массивов и
коллекций, реализующих Ilist. Если описанного недостаточно, то
можно реализовать свой Partitioner согласно требованиям TPL.
21. 21
PLINQ, нюансы
В отличии от первых двух пунктов цепи выполнения запроса, существует
гораздо больше возможностей влияния на его выполнение. Допустимо
следующее:
1. Управлять числом обрабатывающих потоков с помощью метода
WithDegreeOfParallelism.
2. Управлять досрочным прекращением операций с помощью метода
WithCancellation, передав в качестве параметра экземпляр
CancellationToken.
3. Управлять учетом порядка записей исходной коллекции при генерации
результатов с помощью методов AsOrdered/AsUnordered.
var ordersForUpdate = orders.AsParallel()
.AsOrdered()
.WithDegreeOfParallelism(threadsCount)
.WithCancellation(cts.Token)
.Where(UpdateNeeded)
.ToArray();
22. 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);
23. 23
PLINQ, нюансы
Если обработка результатов запроса может быть выполнена
параллельно и ее порядок не важен, то можно
воспользоваться методом ForAll, передав в него тело цикла,
сэкономив на слиянии промежуточных результатов.
orders.AsParallel()
.ForAll(x =>
{
if(UpdateNeeded(x))
UpdateOrder(x);
});
24. 24
Использование ThreadPool
Простейший вариант параллельной обработки — это использование
метода QueueUserWorkItem класса ThreadPool.
while (true)
{
var message = GetMessage(queueName);
if (message != null)
ThreadPool.QueueUserWorkItem(DispatchMessage, message);
else
Thread.Sleep(30000);
}
25. 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();
}
}
26. 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, о новых
событиях нас будет уведомлять сама библиотека.
27. 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.
28. 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.ht
ml
9. Асинхронное программирование в C# 5.0, Алекс Дэвис
10. Concurrency in C# Cookbook, Stephen Cleary
11. C# in depth, Jon Skeet
Примеры: github