2. О Вашем инструкторе
Сергей Тепляков
Visual C# MVP, RSDN Team member
Sergey.Teplyakov@gmail.com
SergeyTeplyakov.blogspot.com
1-2
3. Необходимая подготовка
Слушатели должны:
Быть знакомы с основами языка C#
и платформы .Net
Обладать базовыми знаниями
многопоточности
Быть знакомы с LINQ (Language
Integrated Query)
1-4
4. Roadmap
Введение в реактивные расширения
Дуализм интерфейсов
Основы Rx
Observable sequences
Events и Observables
Observables и асинхронные операции
Concurrency
Новости из Редмонта
1-5
6. Реактивное программирование
Парадигма программирования, ориентированная на потоки
данных и распространение изменений. Это означает, что
должна существовать возможность легко выражать
статические и динамические потоки данных, а также то, что
выполняемая модель должна автоматически распространять
изменения сквозь поток данных.
http://ru.wikipedia.org/
/wiki/Реактивное_программирование
8. Rx - это
1. набор классов, представляющих собой асинхронный
поток данных
2. набор операторов, предназначенных для
манипуляции этими данными
3. набор классов для управления многопоточностью
Rx = Observables + LINQ + Schedulers
9. Зачем все это нужно?
GPS
RSS feeds
Social
media
Server management
10. Интерфейс IEnumerable
Pull-based последовательности представлены
интерфейсом IEnumerable. Это -
коллекции в памяти (списки, вектора и
т.д.)
бесконечные последовательности
(генераторы)
xml-данные
результаты запроса к БД
11. Интерфейс IEnumerable
public interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
} Ковариантность из
.NET 4.0
public interface IEnumerator<out T> : IDisposable
{ Блокирующая
bool MoveNext(); операция
T Current { get; }
void Reset();
}
12. Упрощение интерфейса
IEnumerator
public interface IEnumerator<out T> : IDisposable
{
(T | void | Exception) GetNext();
}
T – следующий элемент
void – окончание
последовательности
Exception – ошибка
16. Простые последовательности
OnComplete
Observable.Empty<int>(); new int[] {};
OnNext
Observable.Return(42); new int[] {42};
OnError
Итератор,
Observable.Throw<int>(ex); генерирующий
исключение
Итератор, который не
Observable.Never<int>(); возвращает
управление
18. Циклы for
var xs = Observable.Generate( var xs = new IEnumerable<int> {
0, for(int i = 0;
x => x < 10, i < 10;
x => x + 1, i++) Предположим,
x => x); yield return i; у нас есть
}; синтаксис
анонимных
итераторов
xs.Subscribe(x => { foreach(var x in xs) {
Console.WriteLine(x); Console.WriteLine(x);
}
});
22. События в .NET
Объявление
event Action<int> E;
Публикация
E(42);
Подписка
E += x => Console.WriteLine(x);
23. Rx
Объявление
ISubject<int> S = new Subject<int>();
Публикация
S.OnNext(42);
Подписка
S.Subscribe(x => Console.WriteLine(x));
24. Events vs Observables
class Program { class Program {
event Action<int> E; ISubject<int> S = new Subject<int>();
static void Main() { static void Main() {
var p = new Program(); var p = new Program();
p.E += x => Console.WriteLine(x); p.S.Subscribe(x => Console.WriteLine(x));
p.E(1); p.S.OnNext(1);
p.E(2); p.S.OnNext(2);
p.E(3); p.S.OnNext(3);
} }
} }
25. Отписка от событий
Events
static event Action<string> E;
//E += x => Console.WriteLine(x);
Action<string> action = x => Console.WriteLine(x);
E += action;
E -= action;
Observables
static ISubject<int> S = new Subject<int>();
var token = S.Subscribe(x => Console.WriteLine(x));
token.Dispose();
26. «События» первого класса
Объект называют «объектом первого класса» когда он:
может быть сохранен в переменной
может быть передан в функцию как параметр
может быть возвращен из функции как результат
может быть создан во время выполнения программы
внутренне самоидентифицируем (независим от
именования)
http://ru.wikipedia.org/wiki/Объект_первого_класса
27. «События» первого класса
// Сохранение в переменной
IObservable<string> textChanged = …;
// Передача в качестве параметра
void ProcessRequests(IObservable<string> input) {…}
// Возвращение в качестве результата
IObservable<int> QueryServer() {…}
28. Возможности наблюдаемых
последовательностей
Преобразование события в поток
объектов Point
IObservable<Point> mouseMoves =
from e in Observable.FromEventPattern<MouseEventArgs>(frm, "MouseMove")
select e.EventArgs.Location;
Фильтрация «событий»
var filtered = mouseMoves.Where(p => p.X == p.Y);
Обработка «событий»
var subscription =
filtered.Subscribe(p => Console.WriteLine("X, Y = {0}", p.X));
Отписка от «событий»
subscription.Dispose();
30. Асинхронные операции
Classical Async Pattern
FileStream fs = File.OpenRead("data.txt");
Невозможность использования
byte[] buffer = new byte[1024]; привычных языковых конструкций
(using, try/finally etc)
fs.BeginRead(buffer, 0, buffer.Length, «Вывернутый» поток исполнения
ar =>
{
int bytesRead = fs.EndRead(ar);
// Обработка данных в массиве buffer
},
null); Сложность обработки ошибок, а
также чтения и сопровождения
кода
31. FromAsyncPattern
static int LongRunningFunc(string s)
{
Thread.Sleep(TimeSpan.FromSeconds(5));
FromAsyncPattern преобразует
return s.Length;
возвращаемое значение метода в
} IObservervable<T>
Func<string, int> longRunningFunc = LongRunningFunc;
Func<string, IObservable<int>> funcHelper =
Observable.FromAsyncPattern<string, int>(
longRunningFunc.BeginInvoke, longRunningFunc.EndInvoke);
IObservable<int> xs = funcHelper("Hello, String");
xs.Subscribe(x => Console.WriteLine("Length is " + x));
32. Tasks vs FromAsyncPattern
Задачи (Tasks) – это унифицированный
способ представления Single value
asynchrony в .Net Framework
Observables – Multi value asynchrony
Существует простой способ
преобразования Tasks -> Observables
33. Task -> ToObservable
Метод расширения можно написать
один раз, а использовать повсюду,
static class FuncExtensions
а не только с Rx
{
internal static Task<int> ToTask(this Func<string, int> func, string s)
{
return Task<int>.Factory.FromAsync(
func.BeginInvoke, func.EndInvoke, s, null);
}
}
Func<string, int> longRunningFunc = LongRunningFunc;
Это более предпочтительный способ,
string s = "Hello, String"; поскольку метод FromAsyncPattern
скоро будет устаревшим
IObservable<int> xs = longRunningFunc.ToTask(s).ToObservable();
xs.Subscribe(x => Console.WriteLine("Length is " + x),
() => Console.WriteLine("Task is finished"));
}
}
34. Чем Rx не является
Rx не заменяет существующей «асинхронности»:
.NET все еще незаменимы
Асинхронные методы все так же применимы в библиотеках
Задачи представляют собой single value asynchrony
Существуют и другие асинхронные источники, как SSIS,
PowerShell, etc
Но rx…
Унифицирует работу
Предоставляет возможность композиции
Предоставляет обобщенные операторы
Так что rx – это …
Мост!
36. Управление многопоточность
Управление многопоточностью
осуществляется с помощью
интерфейса IScheduler
var xs = Observable.Range(0, 10, Scheduler.ThreadPool);
xs.Subscribe(x => Console.WriteLine(x));
Параметризация с помощью
Будет исполняться в контексте
IScheduler доступна в большинстве
планировщика
операций
37. Синхронизация
Обновление пользовательского интерфейса
Label lbl = new Label();
Form frm = new Form() {Controls = {lbl}};
var xs = Observable.Return(42, Scheduler.ThreadPool);
xs.Subscribe(x =>
{
Thread.Sleep(1000);
lbl.Text = "Result is " + x;
});
Использование ObserveOn
xs.ObserveOn(frm).Subscribe(x =>
{
Thread.Sleep(1000); IScheduler поддерживает разные
lbl.Text = "Result is " + x; контексты синхронизации
});
39. Что мы изучили?
Введение в реактивные расширения
Дуализм интерфейсов
Основы Rx
Observable sequences
Events и Observables
Observables и асинхронные операции
Concurrency
1-40
40. Experimental vs Stable
Две версии библиотеки Reactive
Extensions:
Stable
Experimental
Interactive Extensions
41. Где взять?
NuGet
Web - http://msdn.microsoft.com/en-
us/data/gg577610
Can’t find? Use google
42. Дополнительные ссылки
The Reactive Extensions (Rx)... (Data Developer Center) -
http://msdn.microsoft.com/en-us/data/gg577609
Reactive Extensions (MSDN) - http://msdn.microsoft.com/en-
us/library/hh242985(v=vs.103).aspx
Rx Team Blog - http://blogs.msdn.com/b/rxteam/
Реактивные расширения и асинхронные операции -
http://sergeyteplyakov.blogspot.com/2010/11/blog-post.html
43. Roslyn CTP is awailable!
http://msdn.microsoft.com/ru-ru/roslyn
Обращаю внимание, что под «управлением» подразумевается не синхронизация, а «параметризация» определенных методов дополнительными объектами, которые знают, в каком контексте необходимо выполнить определенную операцию. Наиболее типичным примерам такого управления является использование специализированных планировщиков, которые будут вызывать методы наблюдателя в контексте синхронизации.
Rx - это клей, "склеивающий" разнородныеасинхронныеисточникиданных.
Эта пара интерфейс как раз и представляют ту самую pull-последовательность, о которойшларечьраньше. Перебором элементовпоследовательностизанимаетсявызывающий код. Пользовательдолженвызвать метод MoveNext для перехода к следующемуэлементу, и толькоесли (и когда) он вернулtrue (может пройти полдня, кстати), то нужнообратиться к свойствуCurrent для полученияпоследующегоэлементапоследовательности.Условныеобозначения для рисования: IE - IEnumerable и IR - IEnumeratorСпецификторout в объявлении интерфейсов появился только в .NET 4.0 и означает, что этот интерфейс ковариантный. В данном случае это означает, что из этого интерфейса мы можем «вытягивать» данные, а не помещать их туда. И эта однонаправленность интерфейса позволяет его использовать в коде «полиморфным образом». Например, если у нас есть класс Base и его наследник – класс Derived, то в нашем коде мы можем использовать следующее:Derived d = new Derived();Base b = d;Это означает, что переменную с типом наследника мы можем трактовать, как переменную базового класса, но это совсем не значит, что мы можем трактовать последовательность объектов типа Derived, как последовательность типов Base. Точнее, мы не могли этого делать до появления ковариатности и контравариантностиинтрфейсов.Теперь, если обобщенный параметр объявлен со спецификатором out, то этот параметр становится ковариантным. И все, что применялось к простым переменным (нашим переменным b и d), теперь может применяться к интерфесом: IEnumerable<Derived> и IEnumerable<Base>:IEnumerable<Derived> d = new List<Derived>(); IEnumerable<Base> b = d;
Барточеньприкольновыразился, когдасказал, чтозачастуюпаттерны в бандечетырехсгруппированывместе по схожести. Новотсхожестьмеждуитератором и наблюдателемникак не обозначена. В целом, этосвязано с тем, что push-basedколлекцииявляютсялишьчастнымслучаемнаблюдателя, и этотпаттернможетприменяться и в других контекстах. Но, тем не менее, еслипосмотретьподэтимуглом, то симетриямеждуэтимидвумяпаттернамиприсутствует.
Первые три слайда показывают семантическую схожесть (или проводят аналогии) между некоторыми встроенными и простыми наблюдаемыми последовательностями и обычными структурами данных и последовательностями.
Но прежде чем переходить к небольшой демонстрации, давайте рассмотрим еще несколько важных тем.Во-первых, нам нужно рассмотреть упрощенную версию метода Subscribe, который вместо того, чтобы принимать интерфейс IObserver принимает лямбда-выражение. Кроме того, нам нужно рассмотреть процесс отписки от событий, чтобы рассматриваемые пример были более понятными.
Аналогично тому, как вы не можете получить следующий элемент последовательности, пока вы не получили предыдущий, асинхронные последовательности также предоставляют подобные гарантии. (Да, кстати, вы можете добиться этого, но для этого придется попотеть). Точно также, правильная реактивная библиотека, которая следует определенным рекомендациям (существует специальный документ, который называется Rx Design Guidelines) не должна отправлять одному и тому же наблюдателю следующие элемент, пока не завершилась обработка предыдущего элемента.
Тут словам можно сказать, что поскольку у нас нет, как в Джаве анонимной реализации интерфейсов, то мы можем это эмулировать с помощью методов расширения
Запустить голый проект в Visual Studio 2010 с добавленными ссылками на Rx и показать несколько простых и примитивных примеров. В частности, показать, что при добавлении соответствующей using директивы мы получим множество методов расширения, которые позволят нам использовать LINQ во всей красе.Затем, дать пример, который генерирует последовательность данных и немного с ней поиграться.Добавить простой метод Generateс параметром TimeSpan, показать, что основной поток все еще не заблокирован.Потом запустить Generate с random-ом и поиграться еще. Добавить DistinctUntilChanged();, а потом Buffer с печатью всех элементов, а потом с суммой.
Здесь нужно обязательно сказать и, возможно даже показать сложность с отпиской от события. Ведь для нормальной отписки от события
Еще одним недостатком событий, о котором следует упомянуть – это отсутствие композиции. Мы не можем отфильтровать данные одного события и сделать из него другое. Для этого нужно добавить совершенно новое событие. А если нам нужно несколько способов манипулирования (фильтрации, получения проекции), то сложность решения резко возрастает.
Запустить VS2010 и попробовать отфильтровать события пользовательского интерфейса и показать, что из этого получается.
Фраза о том, что Task – это single value asynchrony etc
Очень важно сказать, что RX строятсяon top of existing abstractions but they’re not going to replace them. Остается множество мест, где события нужно использовать напрямую, не оборачивая их ни в какие последовательности. Мы кликнули на форме и получили событие загрузки формы, мы никогда не получим множество событий загрузки формы, которые мы хотели бы композировать каким-то хитрым образом. Если нам нужно обработать событие клика мышки, то опять же, нет никакого смысла добавлять что-либо.Точно так же, все существующие примитивы в .NET Framework остаются такими же ценными, какими они были вчера. Никто не собирается изменять низкоуровневый код, который прекрасно работает и заточен лишь для одной цели. Но теперь, работая с ним его легко можно обернуть в более высокоуровневые примитивы и работать с ним с более высокого уровня абстракции.
Примеры, посвященные асинхронности.
Описать и рассказать об интерфейсе IScheduler, который инкапсулирует в себе вопросы асинхронности и управления ею.По умолчанию используется скеджулер, который называется Immediate, т.е. который не делает никакой многопоточности. И это разумно, поскольку многопоточность не бесплатна. Но практически все операции, типа Return, Range и т.д. принимают дополнительный параметр (т.е. содержат соответствующую перегрузку), который принимает интерфейс Ischeduler.В большинстве случаев об этом параметре можно не думать, поскольку решение по умолчанию нам подходит. Так, например, метод Generate, который принимает еще и TimeSpan по умолчанию использует ThreadPool, поскольку использование текущего потока сделает этот метод бесполезным.
А вот здесь показать, как упадет тот же самый пример без асинхронности, но будет жить, когда мы ее добавим контекст синхронизации!