Вы находитесь на странице: 1из 34

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

программирование

Толстиков Никита
tolstikov.n.s@gmail.com

01/05/2021 Многопоточность 1
План лекции

• APM
• EAP
• TAP
• Concurrent collections

01/05/2021 Толстиков Никита Многопоточность 2


Асинхронное программирование
• Основные паттерны:
– Asynchronous Programming Model (APM)
– Event-based Asynchronous Pattern (EAP)
– Task-based Asynchronous Pattern (TAP)

01/05/2021 Толстиков Никита Многопоточность 3


Asynchronous Programming Model
• Использует асинхронные делегаты и IAsyncResult
объекты
• Основная идея использовать два метода:
– BeginOperationName – для старта асинхронной
операции
– EndOperationName – для получения результатов
• Использовалась в legacy системах до 4.0 .Net
• Depricated

01/05/2021 Толстиков Никита Многопоточность 4


Asynchronous Programming Model
public class AsynPrimeCalcer
{
private delegate int GetPrimeCountHandler(int min, int count);
private static GetPrimeCountHandler ourGetPrimeCountCaller = GetPrimeCount;

public IAsyncResult BeginGetPrimeCount(int min,


int count, AsyncCallback callback, object userState)
{
Console.WriteLine("EndGetPrimeCount on {0}",
Thread.CurrentThread.ManagedThreadId);
return ourGetPrimeCountCaller.BeginInvoke(min, count, callback, userState);
}

public int EndGetPrimeCount(IAsyncResult result)


{
Console.WriteLine("EndGetPrimeCount on {0}",
Thread.CurrentThread.ManagedThreadId);
return ourGetPrimeCountCaller.EndInvoke(result);
}

private static int GetPrimeCount(int min, int count)


{
return ParallelEnumerable.Range(min, count).Count(n =>
Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i =>
n % i > 0));
}
}

01/05/2021 Толстиков Никита Многопоточность 5


Asynchronous Programming Model
public static void PrimeCounterExample()
{
var asyncCalcer = new Slides.AsynPrimeCalcer();
var res1 = asyncCalcer.BeginGetPrimeCount(1, 1000, PrintResults, asyncCalcer);
var res2 = asyncCalcer.BeginGetPrimeCount(1000, 2000, PrintResults, asyncCalcer);
var res3 = asyncCalcer.BeginGetPrimeCount(3000, 40000, null, null);

res3.AsyncWaitHandle.WaitOne();
Console.WriteLine(asyncCalcer.EndGetPrimeCount(res3));
}

public static void PrintResults(IAsyncResult asyncResult)


{
Slides.AsynPrimeCalcer calcer = (Slides.AsynPrimeCalcer) asyncResult.AsyncState;
Console.WriteLine(calcer.EndGetPrimeCount(asyncResult));
}

01/05/2021 Толстиков Никита Многопоточность 6


Event-based Asynchronous Pattern
• Использует асинхронные события и их обработчики
• Основная идея использовать 3 типа методов в
классе:
– MethodNameAsync– для старта асинхронной
операции
– MethodNameCompletedEvent – событие
завершения операции
– MethodNameCancel– для остановки операции
• Depricated

01/05/2021 Толстиков Никита Многопоточность 7


События
• Это механизм класса сообщать его
пользователю, что слчуилось что то
интересное
• Пользователь сам может решать что ему
интерсно – подписываться на события
• Такая модель взаимодействия называется
publisher-subscriber

01/05/2021 Толстиков Никита Многопоточность 8


События
• В С# события – это специальные объекты,
которые при сробатываении запускает все
подписанные на себя делигаты
• Cобытия вводятся при помощи ключевого
слова event

01/05/2021 Толстиков Никита Многопоточность 9


Пример
public static event EventHandler _show;

static void Main()


{
// Add event handlers to Show event.
_show += new EventHandler(Dog);
_show += new EventHandler(Cat);
_show += new EventHandler(Mouse);
_show += new EventHandler(Mouse);

// Invoke the event.


_show.Invoke();
}
static void Cat()
{
Console.WriteLine("Cat");
}
static void Dog()
{
Console.WriteLine("Dog");
}
static void Mouse()
{
Console.WriteLine("Mouse");
}
01/05/2021 Толстиков Никита Многопоточность 10
Event-based Asynchronous
public class AsyncExample
{
public class Method1CompletedEventHandlerArgs : EventArgs
{
}
public delegate void Method1CompletedEventHandler(object sender,
Method1CompletedEventHandlerArgs args);
// Synchronous methods.
public int Method1(string param);
public void Method2(double param);

// Asynchronous methods.
public void Method1Async(string param);
public event Method1CompletedEventHandler Method1Completed;

public void Method2Async(double param);


public void Method2Async(double param, object userState);
public event Method2CompletedEventHandler Method2Completed;

public void CancelAsync(object userState);

public bool IsBusy { get; }

// Class implementation not shown.


}

01/05/2021 Толстиков Никита Многопоточность 11


Event-based Asynchronous Pattern
• Перегрузка методов с использованием userState
позволяет вызывать Async операции не дожидаясь
выполнения предыдущих
• CancelAsync – позволяет остановить запущенную
операцию по userStata
•  WebClient и BackgroundWorker представляют
собой наиболее распространненые примеры EAP

01/05/2021 Толстиков Никита Многопоточность 12


Реализация

здесь

01/05/2021 Толстиков Никита Многопоточность 13


Task-based Asynchronous Pattern
public static Task<int> ReadTask(Stream stream, byte[] buffer, int offset, int count,
object state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}

• Использует Task’и и PFX


• Для обозначения так же используется суффикс
Async
• Каждый метод должен возвращать
System.Threading.Tasks.Task
01/05/2021 Толстиков Никита Многопоточность 14
Реализация
• Ручная
– Метод, который возвращает Task или Task<TResult>
– Программист сам обрабатывает исключения
• Компилятор
– Метод, который возвращает значение, но помечен словом
async
– Компилятор сам генерирует код обработки ошибок и
оборачивания в Task
• Гибридная

01/05/2021 Толстиков Никита Многопоточность 15


Ручная
• Все задачи создаются в ручную: Task.Run()
• Завершение задач ожидается в ручную
• Исключения выбрасываются в месте, где
вызывается свойство Result, метода
Wait() или
Task.WaitAll()/Task.WaitAny()
• Все исключения из Task обернуты в
AggregationException

01/05/2021 Толстиков Никита Многопоточность 16


Ручная
public class TaskPrimeCalcer
{
public delegate Task GetPrimeCountEventHandler(object sender, GetPrimeEventArg e);
public event GetPrimeCountEventHandler GetPrimeCountClick;
private PrimeCalcer myPrimeCalcer = new PrimeCalcer();

public Task Click(int min, int count)


{
return GetPrimeCountClick.Invoke(this, new GetPrimeEventArg {Count = count,
Minimum = min});
}

public Task<int> Calc(int min, int count)


{
return Task.Run(() => myPrimeCalcer.GetCount(min, count));
}

public Task<int> CalcAsync(int min, int count)


{
return Task.Run(() => myPrimeCalcer.GetCountMultithread(min, count));
}
}

01/05/2021 Толстиков Никита Многопоточность 17


Ручная
public static Task ButtonClick_Event(object sender, Slides.GetPrimeEventArg e)
{
return Task.Run(() =>
{
var primeCalcer = (Slides.TaskPrimeCalcer) sender;
return primeCalcer.Calc(e.Minimum, e.Count).Result;
}).ContinueWith(task =>
Console.WriteLine("From: {0} {1} numbers - {2}", e.Minimum, e.Count, task.Result)
);
}

...

var taskPrime = new Slides.TaskPrimeCalcer();


taskPrime.GetPrimeCountClick += ButtonClick_Event;

using (new OperationTimer("Async tasks"))


{
Task.WaitAll(taskPrime.Click(1, 10000000),
taskPrime.Click(10000000, 10000000),
rime.Click(20000000, 10000000));
}

01/05/2021 Толстиков Никита Многопоточность 18


Компилятор
• Все задачи создаются в ручную :
Task.Run()
• Вводится при помощи ключевых слов
async/await
• await – асинхронное ожидание, не
блокирует поток, а ставит его на паузу
• Исключение выбрасывается в await
• Все исключения перебрасываются в
первозданном виде
01/05/2021 Толстиков Никита Многопоточность 19
Компилятор
public static async Task ButtonClick_EventAsync(object sender, Slides.GetPrimeEventArg e)
{
var primeCalcer = (Slides.TaskPrimeCalcer)sender;
int result = await primeCalcer.Calc(e.Minimum, e.Count);
Console.WriteLine("From: {0} {1} numbers - {2}", e.Minimum, e.Count, result);
}
...

var taskPrime = new Slides.TaskPrimeCalcer();


taskPrime.GetPrimeCountClick += ButtonClick_Event;

using (new OperationTimer("Await tasks async"))


{
await Task.WhenAll(taskPrime.Click(1, 10000000),
taskPrime.Click(10000000, 10000000),
taskPrime.Click(20000000, 10000000));
}

01/05/2021 Толстиков Никита Многопоточность 20


async/await
• Ключевое слово await должно идти в паре с async
• await – это оператор, который манипулирует
awaitable объектами
• Task – awaitable объект и для облегчения работы с
ними введены методы расширения, возвращающие
Task:
– Task.Delay – аналог Thread.Sleep
– Task.WhenAny – аналог Task.WaitAny
– Task.WhenAll – аналог Task.WaitAll
– Task.Run или TaskFactory.StartNew вместо создания и
запуска задачи
01/05/2021 Толстиков Никита Многопоточность 21
async/await
• Оператор async превращает метод в
statemachine:

01/05/2021 Толстиков Никита Многопоточность 22


async/await
• Оператор async превращает метод в
statemachine:

01/05/2021 Толстиков Никита Многопоточность 23


Применение
• TAP паттерн является наиболее приемлемым
и удобным решением для асинхронного
программирования на C# поддерживаемым
на уровне компилятора
• При помощи TAP паттерна можно
реализовывать и I/O операции и вычисления
• При написании библиотек TAP паттерн
лучше использовать для I/O операций, а
большие вычисления проводить синхронно
01/05/2021 Толстиков Никита Многопоточность 24
Применение при вычислениях
• Генерировать вычислительные можно при
помощи:
– Task.Factory.StartNew(…) (Task.Run c 4.5)
– Создать Task и вызвать Start (когда создание отделенно
от запуска)
– ContinueWith – для генерации новой задачи, которая
запустится после выполнения
– Статические у TaskFactory ContinueWhenAll и
ContinueWhenAny
• Для long run задач, лучше всегда передавать
CancelationToken
01/05/2021 Толстиков Никита Многопоточность 25
CancelationToken
public Task<Bitmap> RenderAsync(
ImageData data, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var bmp = new Bitmap(data.Width, data.Height);
for(int y=0; y<data.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for(int x=0; x<data.Width; x++)
{
… // render pixel [x,y] into bmp
}
}
return bmp;
}, cancellationToken);
}

• Task завершится в состоянии Canceled, если:


– Запрос на завершение придет раньше чем задача перейдет в
состояние Running
– OperationCanceledException не будет обработана телом задачи
01/05/2021 Толстиков Никита Многопоточность 26
Применение при I/O
• Для совершения I/O опреций и задач, не
требуищих для завершения основной поток лучше
использовать TaskCompletionSource<TResult>
public static Task<Socket> AcceptAsync(this Socket socket)
{
if (socket == null) throw new ArgumentNullException("socket");
var tcs = new TaskCompletionSource<Socket>();
socket.BeginAccept(asyncResult => {
try
{
var s = asyncResult.AsyncState as Socket;
var client = s.EndAccept(asyncResult);
tcs.SetResult(client);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, socket);
return tcs.Task;
}

var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610));
listener.Listen(10); var client = await listener.AcceptAsync();

01/05/2021 Толстиков Никита Многопоточность 27


Concurrent collections
• Потоко-безопасные коллекции находятся в
System.Collections.Concurrent
• Потоко-безопасные коллекции во всем уступают
обычным коллекциям, кроме потоко-
безопасности
• Использование этих коллекций не делает ваш
код потоко-безопасным
• Модификация во время итерации не
выбрасывает исключение

01/05/2021 Толстиков Никита Многопоточность 28


IProducerConsumerCollection<T>
• Производитель/потребитель коллекции
выполняют два действия:
– Добавить элемент в коллекцию (produce)
– Получить элемент из коллекции, удалив
его (consume)
• Классический пример Stack и Queue
• Частый прецедент при многопочном
исполнении
01/05/2021 Толстиков Никита Многопоточность 29
IProducerConsumerCollection<T>
public interface IProducerConsumerCollection<T>
: IEnumerable<T>, ICollection, IEnumerable
{
bool TryAdd(T item); //Attempts to add an object to the
//collection
bool TryTake(out T item); //Attempts to remove and return an
//object from the collection
T[] ToArray(); //Copies the elements contained in the to a new
//array
}

• Наследники коллекции:
– ConcurrentStack<T>
– ConcurrentQueue<T>
– ConcurrentBag<T>
01/05/2021 Толстиков Никита Многопоточность 30
ConcurrentBag<T>
public class ConcurrentBag<T> : IProducerConsumerCollection<T>,
IEnumerable<T>, ICollection, IEnumerable
{
...
}

• Потоко-безопасная коллекция
неупорядочынх объектов с дубликатами
• Предоставляет отдельный список для
каждого потока
• Используется при многопоточной
обработке коллекций
01/05/2021 Толстиков Никита Многопоточность 31
BlockingCollection<T>
• Обертка над
IProducerConsumerCollection,
• Добавляет метод Take, который блокирует
коллекцию пока в ней не появятся элементы
• В конструкторе принимает коллекцию
• По умолчанию Queue
• Можно взять GetConsumingEnumerable,
который будет итерироваться по элементам в
процессе их появления
01/05/2021 Толстиков Никита Многопоточность 32
BlockingCollection<T>
public class PCQueue : IDisposable
{
BlockingCollection<Action> _taskQ = new BlockingCollection<Action>();
public PCQueue(int workerCount)
{
// Create and start a separate Task for each consumer:
for (int i = 0; i < workerCount; i++)
Task.Factory.StartNew(Consume);
}

public void Dispose() { _taskQ.CompleteAdding(); }

public void EnqueueTask(Action action) { _taskQ.Add(action); }

void Consume()
{
// This sequence that we’re enumerating will block when no elements
// are available and will end when CompleteAdding is called.
foreach (Action action in _taskQ.GetConsumingEnumerable())
action(); // Perform task.
}
}

01/05/2021 Толстиков Никита Многопоточность 33


The End

01/05/2021 Толстиков Никита LINQ 34