Post on 18-Jul-2015
Асинхронне програмування в .NET
Олександр Павлишак
травень 2012
Для кого
Початківці
Ідея асинхронності
Базові техніки
Досвідчені
Поточний стан справ
Нові бібліотеки та підходи
Структуризація, порівняння
Outside .NET
Нові ідеї, розширення світогляду
План
Асинхронність intro
Asynchronous Programming Model (APM)
Tasks (Task Parallel Library)
Reactive Extensions (Rx)
C# 5.0 async
Асинхронність
– Ініціювати виконання операції
– Не чекати поки операція закінчиться,
– а одразу повернути виконання
– Продовжувати займатись іншими справами
– Могти скасувати операцію
– Отримати сповіщення про результат операції (успішний/неуспішний)(callback)
== відсутність блокування
На клієнті
UI потік не можна блокувати
Типові асинхронні операції:
– Запити до серверів (веб, БД, sockets) – IO bound
– Читання/запис на диск – IO bound
– Довготривалі обчислення – CPU bound
Основна незручність:
– результат асинхронної операції повинен бути оброблений в UI потоці
Основна мета – responsiveness
Бажане функціонування UI потоку
– Багато дрібних подій, виконання кожної з яких швидко завершується
– Події – message pump events + async operations callbacks
– Новим подіям не приходиться довго чекати на початок виконання
Hardware працює асинхронно
CPU не блокується на операціях з пам’яттю, диском, мережевим адаптером
Всі IO пристрої володіють затримкою (latency)
Непередбачуваною
Підтримка OS: Completion ports
Недолік –програмна модель досить складна
O RLY?
IO-bound задачі можуть виконуватись фоново (на апаратному рівні)
Як наслідок:
Не потрібні лишні потоки програми з логікою обробки результатів IO
Цим може займатись UI потік,
якщо ця обробка швидка.
Потрібна зручна абстракціяCompletion ports занадто низькорівневі
Плюс є ще CPU-bound задачі
На сервері
Задача – обробляти багато клієнтських запитів одночасно
(concurrency)
Модель IIS + ASP.NET: 1 request – 1 thread1000 запитів – 1000 потоків
1000 запитів – 30 потоків + 970 запитів в черзі
Оптимальна кількість потоків = кількості CPU
Неефективне використання ресурсів:Більшість часу потоки чекають на IO
А коли прокидаються, конкурують за CPU
Основна мета – масштабованість (scalability)
Responsiveness and scalabilityдві мети асинхронного програмування
Callbacksзасіб реалізації, який володіє проблеми :(
Потрібне розширення абстракції callback’a,а також кращі абстракції замість callback’ів
Простий випадок: виконуємо одну операцію асинхронно(лише happy path)
Demo:
UI (load/save values)APM (Asynchronous Programming Model)
Альтернативи: окремий потік + UI через dispatcher
ASP.NET (call web service)APM
приклади тут
stream.BeginRead(buffer, 0, buffer.Length, asyncResult =>{
context.Post(_ =>{
try{
stream.EndRead(asyncResult);stream.Dispose();Input = Encoding.ASCII.GetString(buffer);
}catch{
Input = "Error!!!";}finally{
DisplayLoadCompletedNotification();}
}, null);}, null);
Can throw
Execute in UI thread
Does not block
Does not block here
Captured in closure
“Awesomeness” of callbacks
Все ускладнюється
Обробка помилок
Cancellation
Запуск багатьох операцій послідовно
Запуск декількох операцій паралельно+ після закінчення сповістити користувача
Координація операційпісля закінчення двох викликів запустити третій
...));}}}));
Wait, this is not LISP!
APM
Запуск: fileStream.BeginRead(…)
Callback: BeginRead(…, asyncResult => { … }, …)Викликає EndRead()
SynchronizationContext – щоб повернутись в UI потік
Результат: виклик EndRead()
Exceptions: виклик EndRead()try…catch навколо EndRead() всередині callback
Де:Thread from IO thread pool
Явне використання SynchronizationContext.Post()
Composition: :(
Futures and Promises
Розділення ініціювання операції від отримання результату
Func<>, Action<> – навпаки
Реалізація – Task<T>Обох понять, future and promise
Обох видів, IO-bound and CPU-bound
Block on Task.Result
Continuation with Task.ContinueWith()
Task.Result повідомляє про Exception
RestoreInput().ContinueWith(restoreInputTask =>
{try{
Input = restoreInputTask.Result;}catch{
Input = "Error!!!";}finally{
DisplayLoadCompletedNotification();}
}, TaskScheduler.FromCurrentSynchronizationContext());
Returns Task<string> Called when task completes
Does not block Rethrows
Where to execute continuation
Already started
In UI thread
Task returned by RestoreInput()
We can schedule several continuations
Task<string> RestoreInput(){
return Task.Factory.StartNew(() =>{
return File.ReadAllText("savedInput.txt");});
}
Returns started task
Runs in thread poolby default
Exception will be re-thrown in Task.Result
Can specify Scheduler
Запуск Task’ів
TaskFactory.FromAsync()Адаптація APM-моделі до Task-моделі
TaskFactory.StartNew(() => { return …; })Адаптація будь-якої моделі до Task
Вказується TaskScheduler
Вказується CancellationToken
Demo
Save/load values with TPL
Tasks
Запуск: TaskFactory.FromAsync()TaskFactory.StartNew()
Callback: Task.ContinueWith()Викликає Task.Result
Результат: Task.Result in continuationExceptions: виклик Task.Result
try…catch навколо Task.Result всередині callbackTask.Exception, Task.IsFaulted, Task.Status
Де:При запуску вказується TaskSchedulerВ ContinueWith() вказується TaskScheduler
Composition: ContinueWith(), WhenAll/Any()
Events
Корисна абстракція, не лише для UIFirst class (F#), Delegates (C#), GOF-style Observers
Single events: button click, request received
Event streams: mouse moves,key presses, stream of tweets
Події часто асинхронніПроблема – погано компонуються
Unless first class
Уявіть композицію
Швидкий пошук, фільтр
Уявіть композицію
По суті – серія подій OnTextChanged
Рядок повинен обрізатись – String.Trim()Tabs spaces; multiple spaces single space
Рядки із спец. символами повинні виключатись
Запуск пошуку – коли користувач перестане друкувати – throttling
Пошук лише значень, які відрізняються(послідовних) – distinct
Відображення результату останнього пошуку а не того, який прийшов найпізніше
Запис в історію пошуку
IFs, IFs, IFs
Timer
Shared mutable var
More mutable state
Multiple subscribers
Reactive Extensions
Серія OnTextChanged = stream of TextBox.Texts
Звучить як... IEnumerable<string>?Pull-based: T MoveNext(void)
IObservable<string>!Push-based: void OnNext(T)
Functional Programming! Monads! Composition! Pure functions! Say No to Mutable State! Support cancer research!
Lambda
Observer ≈≈ Iterator
interface IEnumerator<out T>{
T Current { get; }
bool MoveNext(void);}
interface IObserver<in T>
{
void OnNext(T);
void OnError(Exception);
void OnCompleted();
}
interface X<out T>{
T|Exception X(void)
bool X(void);}
interface X<in T>
{
void X(T);
void X(Exception);
void X(bool);
}
Observer ≈≈ Iterator
interface IObservable<out T>
{
IDisposable Subscribe(Observer<T>);
}
interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator(void);
}
textChanges.Select(s => s.Trim()).Where(s => s != "").Subscribe(onNext:
s => Console.WriteLine(s));
IObservable<string>IObservable<string>
Runs on each received string
Can also pass onError, onCompleted
Returns IDisposable for un-subscription
Reactive Extensions (Rx)
A library for composing asynchronous and event-based programs using observable
sequences and LINQ-style query operators.
Rx = Observables + LINQ + Schedulers
Official site
Demo
Single event – Save/Load settings
Event stream – TextBox.Text changes
Rx
Запуск: багато шляхів, включаючи:Observable.Return(), Observable.FromAsyncPattern()
Callback: IObserver.OnNext()IObservable.Subscribe(item => { … })
Результат: IObserver.OnNext()
Exceptions:IObserver.OnError(Exception)
Catch(), Finally(), OnErrorResumeNext() combinators
Де:ObserveOn(IScheduler)
Composition: широкий набір комбінаторів, LINQ
Rx – функціональний шлях вирішення проблем з асинхронністю
та concurrency
Як щодо імперативного стилю?
Monads! Composition! Purity!
Імперативний стиль
Наскільки sync код відрізняється від async?
Суттєво відрізняється!
хіба що ви програмували у функціональному стилі з самого початку
Як конвертувати sync код в async?
Перетворювати в continuations – CPS
Як бути з while/for/foreach?
try…catch? finally? using() {…}?
hint hint :)
Після перетворення
Перетворений код буде
– Рекурсивний (tail recursion, anyone?)
– Реалізовувати машину станів (goto is back!)
Хороша новина: така трансформація є механічна
Compiler can do it automagicallyforeach
yield return
Pattern-based
foreach over non-IEnumerable
Імперативний sync код
try{
DisplayLoadingInProgressNotification();Input = RestoreInput();
}catch{
Input = "Error!!!";}finally{
DisplayLoadCompletedNotification();}
async
try{
DisplayLoadingInProgressNotification();Input = await RestoreInput();
}catch{
Input = "Error!!!";}finally{
DisplayLoadCompletedNotification();}
Ще приклад: цикл
try{
DisplayLoadingInProgressNotification();foreach (var textBox in InputTextBoxes){
try{
textBox.Text = RestoreInput(GetFileName(textBox));
}catch{ textBox.Text = "Error!!!"; }
}}finally{ DisplayLoadCompletedNotification(); }
async з циклом
try{
DisplayLoadingInProgressNotification();foreach (var textBox in InputTextBoxes){
try{
textBox.Text = await RestoreInput(GetFileName(textBox));
}catch{ textBox.Text = "Error!!!"; }
}}finally{ DisplayLoadCompletedNotification(); }
await non-thread-pool Task
async Task<string> RestoreInput(){
using (var reader = File.OpenText("savedInput.txt")){
var result = await reader.ReadToEndAsync();Debug.WriteLine("Input restored from file.");return result;
}}
await thread-pool Tasks
if (File.Exists(fileName))
return await Task.Run(() =>
{
return File.ReadAllText(fileName);
});
else
return String.Empty;
Demo
async save/load value
async save/load multiple values in sequence
async save/load multiple values in parallel
async, await
Запуск: call async APIReturning Task, WinRT or any awaitable
Callback: код, який слідує після awaitабо Task.ContinueWith()
Результат: await
Exceptions: await
Де: в поточному SynchronizationContextМожна також заборонити context capturing;
Composition: імперативний кодТакож Tasks
APM, Tasks, Rx, asyncAPM
- Callback model
- Композиція складна
Tasks+/- Callbacks
+ Краща композиція, обробка помилок, cancellation
Rx++ Композиція, обробка помилок та cancellationу функціональному стилі
- Learning curve
async+/- Композиція, обробка помилок в імперативному стилі
+ Конвертується в Tasks
Дякую за увагу!
TPL Home Team Blog Free book Other book
Reactive Extensions Home C9-Videos Intro
async Home Spec Jon Skeet InfoQ WinRT Deep
Презентація slideshare.net/opavlyshak
Приклади github.com/opavlyshak/dotNet-async-demos
@pavlyshak
pavlyshak@gmail.com