SlideShare a Scribd company logo
Живые приложения с Rx
Почему	
  Reac,ve	
  Extensions?	
  
•  Отзывчивость	
  приложений	
  
•  Упрощение	
  кода	
  работы	
  со	
  временем	
  
•  Упрощение	
  управления	
  потоками	
  
•  Упрощение	
  подписки-­‐отписки	
  для	
  событий	
  
•  Тестируемость	
  кода	
  
•  LINQ	
  для	
  событий	
  и	
  потоков	
  данных	
  
•  Наличие	
  реализаций	
  для	
  других	
  языков	
  
программирования	
  
2	
  
IEnumerable<T>	
  vs	
  IObservable<T>	
  
3	
  
Преимущества	
  Rx	
  перед	
  событиями	
  
•  События	
  однопоточны.	
  Все	
  обработчики	
  будут	
  вызываться	
  по	
  очереди	
  
в	
  потоке	
  который	
  запустил	
  событие.	
  
•  Если	
  в	
  одном	
  из	
  обработчиков	
  события	
  будет	
  выброшено	
  исключение	
  
все	
  остальные	
  обработчики	
  не	
  будут	
  вызваны	
  
•  Все	
  подписки	
  на	
  события	
  необходимо	
  отписывать.	
  Для	
  этого	
  придется	
  
хранить	
  ссылку	
  на	
  метод	
  для	
  отписки	
  и	
  объект	
  
•  Rx	
  поддерживает	
  async/await	
  для	
  запросов	
  требующих	
  ожидания	
  
конкретного	
  элемента	
  
•  Rx	
  предоставляет	
  методы	
  для	
  управления	
  временем	
  и	
  потоками,	
  а	
  
также	
  тестовые	
  реализации	
  управляющих	
  объектов.	
  	
  
•  Rx	
  предоставляет	
  методы	
  для	
  связи	
  нескольких	
  потоков	
  данных	
  в	
  
один	
  
•  Rx	
  предоставляет	
  возможность	
  обрабатывать	
  данные	
  аналогично	
  
LINQ-­‐запросу	
  в	
  функциональном	
  стиле.	
  
4	
  
Основные	
  интерфейсы	
  Rx	
  
•  IObservable<T>	
  
	
  	
  	
  IDisposable	
  Subscribe(IObserver<T>	
  observer);	
  
•  IObserver<T>	
  
	
  void	
  OnNext(T	
  value);	
  
	
  void	
  OnError(Exception	
  error);	
  
	
  void	
  OnCompleted();	
  
•  IScheduler	
  
•  ISubject<T>	
  :	
  IObservable<T>, IObserver<T>
5	
  
Простой	
  класс	
  с	
  Rx	
  
	
  	
  	
  	
  public	
  class	
  LogoutManager	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  IObservable<Unit>	
  Logout	
  {	
  get;	
  private	
  set;	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  IObserver<Unit>	
  UserActionsObserver	
  {	
  get;	
  private	
  set;	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  IObserver<Unit>	
  LogoutCommandsObserver	
  {	
  get;	
  private	
  set;	
  }	
  	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  LogoutManager(TimeSpan	
  timeout)	
  
	
  	
  	
  	
  	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  var	
  userActionsSubject	
  =	
  new	
  Subject<Unit>();	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  var	
  logoutSubject	
  =	
  new	
  Subject<Unit>();	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  UserActionsObserver	
  =	
  userActionsSubject.AsObserver();	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  LogoutCommandsObserver	
  =	
  logoutSubject.AsObserver();	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  Logout	
  =	
  userActionsSubject	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .StartWith(Unit.Default)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Throttle(timeout)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge(logoutSubject);	
  
	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  }	
  
6	
  
Потоки	
  данных	
  в	
  LogoutManager	
  
	
  Logout	
  =	
  userActionsSubject	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .StartWith(Unit.Default)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Throttle(timeout)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge(logoutSubject);	
  
7	
  
Интеграция	
  Rx	
  с	
  существующими	
  
событиями	
  
•  Короткая:	
  
Observable.FromEventPattern(source,	
  "PropertyChanged");	
  
•  Типобезопасная:	
  
Observable.FromEventPattern	
  
<PropertyChangedEventHandler,PropertyChangedEventArgs>(
	
  	
  	
  	
  	
  	
  	
  handler	
  =>	
  source.PropertyChanged	
  +=	
  handler,	
  
	
  	
  	
  	
  	
  	
  	
  handler	
  =>	
  source.PropertyChanged	
  -­‐=	
  handler)	
  
•  Промежуточные	
  варианты	
  с	
  указанием	
  типов	
  только	
  
для	
  части	
  операндов	
  
	
  
	
   8	
  
Текстовый	
  фильтр	
  на	
  Rx	
  
9	
  
Текстовый	
  фильтр	
  на	
  Rx	
  
	
  Observable.FromEventPattern	
  
<TextChangedEventHandler,	
  TextChangedEventArgs>(
	
  	
  handler	
  =>	
  filterTextBox.TextChanged	
  +=	
  handler,
	
  	
  handler	
  =>	
  filterTextBox.TextChanged	
  -­‐=	
  handler)
	
  	
  	
  	
  	
  	
  .Select(x	
  =>	
  x.Sender)
	
  	
  	
  	
  	
  	
  .Cast<TextBox>()
	
  	
  	
  	
  	
  	
  .Select(x	
  =>	
  x.Text)
	
  	
  	
  	
  	
  	
  .Where(x	
  =>	
  x.Length	
  >	
  2)
	
  	
  	
  .Subscribe(FilterCollection);	
  
10	
  
Расширенный	
  фильтр	
  на	
  Rx	
  
•  Исходная	
  коллекция	
  также	
  может	
  меняться	
  
•  Пока	
  пользователь	
  продолжает	
  ввод	
  не	
  имеет	
  смысла	
  
перезапускать	
  поиск	
  
•  Логика	
  фильтрации	
  может	
  быть	
  достаточно	
  сложна	
  и	
  
отнимать	
  много	
  времени	
  
•  Пользователь	
  должен	
  видеть	
  результаты	
  по	
  мере	
  их	
  
поступления,	
  а	
  не	
  после	
  завершения	
  фильтрации	
  
•  Пользователь	
  может	
  начать	
  вводить	
  фильтр,	
  а	
  затем	
  
вернуть	
  старый.	
  Повторную	
  фильтрацию	
  при	
  этом	
  не	
  
надо	
  запускать	
  
11	
  
Расширенный	
  фильтр	
  на	
  Rx	
  
	
  public	
  interface	
  IReactiveCollection<TItem>	
  :	
  	
   	
  
	
   	
   	
  INotifyPropertyChanged,	
  IDisposable
{
	
  	
  //	
  Источник	
  данных	
  (полная	
  коллекция)	
  
	
  	
  	
  	
  	
  	
  	
  	
  TItem[]	
  Source	
  {	
  get;	
  set;	
  }	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  //	
  Отображаемая	
  часть	
  данных	
  (отфильтрованная	
  коллекция)	
  
	
  	
  	
  	
  	
  	
  	
  	
  TItem[]	
  View	
  {	
  get;	
  }	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  //	
  Строка	
  фильтра	
  
	
  	
  	
  	
  	
  	
  	
  	
  string	
  Filter	
  {	
  get;	
  set;	
  }	
  	
  	
  	
  
	
  	
  	
  }	
  
12	
  
Расширенный	
  фильтр	
  на	
  Rx	
  
public	
  ReactiveCollection(Func<TItem,	
  string,	
  bool>	
  filterFunc)	
  {
	
  	
  	
  	
  	
  var	
  buffer	
  =	
  new	
  ObservableCollection<TItem>();	
  
	
  	
  	
  	
  	
  _subscription	
  =	
  this.PropertyChangedAsObservable(x	
  =>	
  x.Filter)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Throttle(TimeSpan.FromMilliseconds(200))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(_	
  =>	
  string.IsNullOrWhiteSpace(Filter)	
  ||	
  Filter.Length	
  >	
  2)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .DistinctUntilChanged(_	
  =>	
  Filter)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge(this.PropertyChangedAsObservable(x	
  =>	
  x.Source))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Do(_	
  =>	
  buffer.Clear())
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Select(_	
  =>	
  Source.EmptyIfNull()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .ToObservable()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(x	
  =>	
  filterFunc(x,	
  Filter))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Buffer(TimeSpan.FromMilliseconds(200))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Do(x	
  =>	
  x.ForEach(buffer.Add)))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Switch()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Subscribe(_	
  =>	
  View	
  =	
  buffer.ToArray());	
  
}
13	
  
Потоки	
  данных	
  в	
  расширенном	
  
фильтре	
  
this.PropertyChangedAsObservable(x	
  =>	
  x.Filter)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Throttle(TimeSpan.FromMilliseconds(200))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(_	
  =>	
  string.IsNullOrWhiteSpace(Filter)	
  ||	
  Filter.Length	
  >	
  2)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .DistinctUntilChanged(_	
  =>	
  Filter)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge(this.PropertyChangedAsObservable(x	
  =>	
  x.Source))
14	
  
Потоки	
  данных	
  в	
  расширенном	
  
фильтре	
  
	
   	
  .Select(_	
  =>	
  Source.EmptyIfNull()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .ToObservable()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(x	
  =>	
  filterFunc(x,	
  Filter))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Buffer(TimeSpan.FromMilliseconds(200))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Do(x	
  =>	
  x.ForEach(buffer.Add)))
	
  	
  	
  	
  	
  	
  	
  	
  	
  .Switch()	
  
15	
  
Управление	
  потоками	
  в	
  Rx	
  
public	
  ReactiveCollection(Func<TItem,	
  string,	
  bool>	
  filterFunc,	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  )	
  {
	
  	
  	
  	
  	
  var	
  buffer	
  =	
  new	
  ObservableCollection<TItem>();	
  
	
  	
  	
  	
  	
  _subscription	
  =	
  this.PropertyChangedAsObservable(x	
  =>	
  x.Filter)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Throttle(TimeSpan.FromMilliseconds(timeout))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(_	
  =>	
  string.IsNullOrWhiteSpace(Filter)	
  ||	
  Filter.Length	
  >	
  3)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .DistinctUntilChanged(_	
  =>	
  Filter)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge(this.PropertyChangedAsObservable(x	
  =>	
  x.Source))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Do(_	
  =>	
  buffer.Clear())
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Select(_	
  =>	
  Source.EmptyIfNull()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .ToObservable(	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  )
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(x	
  =>	
  filterFunc(x,	
  Filter))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Buffer(TimeSpan.FromMilliseconds(timeout))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Do(x	
  =>	
  x.ForEach(buffer.Add)))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Switch()	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .ObserveOn(observeOn)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Subscribe(_	
  =>	
  View	
  =	
  buffer.ToArray());	
  
}
IScheduler	
  filterScheduler,	
  IScheduler	
  observeOn	
  	
  
filterScheduler	
  
16	
  
Применение	
  фильтра	
  на	
  Rx	
  
17	
  
Проблемы	
  реализации	
  при	
  помощи	
  
событий	
  
•  Необходимость	
  реализации	
  задержки	
  для	
  
событий	
  –	
  для	
  того,	
  чтобы	
  не	
  загружать	
  списки	
  
клиентов	
  и	
  счетов	
  много	
  раз	
  
•  Постоянные	
  подписки	
  и	
  отписки	
  на	
  события	
  
элементов	
  коллекции	
  клиентов,	
  отсутствие	
  
отписок	
  быстро	
  приведет	
  к	
  утечке	
  памяти	
  в	
  
приложении.	
  
•  Код	
  для	
  управления	
  потоками	
  –	
  фильтрация	
  в	
  
отдельном	
  потоке,	
  вывод	
  результатов	
  в	
  другом	
  
18	
  
Применение	
  фильтра	
  на	
  Rx	
  
Companies	
  =	
  new	
  ReactiveCollection<Company>((x,	
  y)	
  =>	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  
	
  	
  	
  	
  string.IsNullOrWhiteSpace(y)	
  ||	
  x.Name.Contains(y))
	
  {	
  	
  	
  	
  	
  Source	
  =	
  CompanyList	
  	
  	
  	
  };	
  
	
  
	
  Clients	
  =	
  new	
  ReactiveCollection<Client>((x,	
  y)	
  =>	
   	
  	
  	
   	
  
	
  	
  	
  	
  	
  string.IsNullOrWhiteSpace(y)	
  ||	
  x.Name.Contains(y));	
  
	
  Accounts	
  =	
  new	
  ReactiveCollection<Account>(SlowFilter);	
  
	
  private	
  bool	
  SlowFilter(Account	
  x,	
  string	
  y)
	
  {
	
  	
  Thread.Sleep(random.Next(50,	
  250));
	
  	
  return	
  string.IsNullOrWhiteSpace(y)	
  ||	
  x.Number.ToString().Contains(y);
	
  }
	
  
19	
  
Применение	
  фильтра	
  на	
  Rx	
  
	
  Companies.Source.Select(x	
  =>	
  x.PropertyChangedAsObservable(y	
  =>	
  y.Selected))
	
  	
  	
  	
  	
  	
  .Merge()
	
  	
  	
  	
  	
  	
  .Throttle(TimeSpan.FromMilliseconds(200))
	
  	
  	
  	
  	
  	
  .ObserveOnDispatcher()
	
  	
  	
  	
  	
  	
  .Subscribe(_	
  =>	
  Clients.Source	
  =	
  GetClients(Companies.Source
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(x	
  =>	
  x.Selected)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Select(x	
  =>	
  x.Id)));	
  
	
  
	
  Clients.PropertyChangedAsObservable(x	
  =>	
  x.Source)
	
  	
  	
  	
  .Select(_	
  =>	
  Clients.Source)
	
  	
  	
  	
  .Select(x	
  =>	
  x.Select(y	
  =>	
  y.PropertyChangedAsObservable(z	
  =>	
  z.Selected))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Throttle(TimeSpan.FromMilliseconds(100)))
	
  	
  	
  	
  .Switch()
	
  	
  	
  	
  .ObserveOnDispatcher()
	
  	
  	
  	
  .Subscribe(_	
  =>	
  Accounts.Source	
  =	
  GetAccountSource(Clients.Source 	
  	
  	
  	
  	
  
	
  	
  	
  	
  	
  	
  	
  .Where(x	
  =>	
  x.Selected)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Select(x	
  =>	
  x.Id)));	
  
20	
  
А	
  что	
  с	
  тестированием?	
  
•  Управление	
  временем	
  
•  Управление	
  потоками	
  
•  Буферизация	
  
	
  
•  Скорость	
  выполнения	
  тестов	
  
21	
  
Тестирование	
  Rx	
  при	
  помощи	
  
интерфейса	
  IScheduler	
  
	
  	
  	
  	
  public	
  class	
  LogoutManager	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  IObservable<Unit>	
  Logout	
  {	
  get;	
  private	
  set;	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  IObserver<Unit>	
  UserActionsObserver	
  {	
  get;	
  private	
  set;	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  IObserver<Unit>	
  LogoutCommandsObserver	
  {	
  get;	
  private	
  set;	
  }	
  	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  public	
  LogoutManager(TimeSpan	
  timeout,	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  )	
  
	
  	
  	
  	
  	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  var	
  userActionsSubject	
  =	
  new	
  Subject<Unit>();	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  var	
  logoutSubject	
  =	
  new	
  Subject<Unit>();	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  UserActionsObserver	
  =	
  userActionsSubject.AsObserver();	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  LogoutCommandsObserver	
  =	
  logoutSubject.AsObserver();	
  
	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  Logout	
  =	
  userActionsSubject.StartWith(Unit.Default)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Throttle(timeout,	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  )	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge(logoutSubject);	
  
	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  	
  	
  	
  public	
  LogoutManager(TimeSpan	
  timeout)	
  :	
  this(timeout,	
  Scheduler.Default)	
  	
  {	
  }	
  
	
  	
  	
  	
  }	
  
	
  IScheduler	
  scheduler	
  
scheduler	
  
22	
  
Тестирование	
  Rx	
  
[Test]	
  	
  
public	
  void	
  OnInactivity_Logout()	
  	
  
{
	
  	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  	
  var	
  manager	
  =	
  new	
  LogoutManager(TimeSpan.FromMinutes(5),	
  testScheduler);
	
  	
  	
  	
  	
  manager.Logout.Subscribe(_	
  =>	
  Assert.Pass());
	
  	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMinutes(6).Ticks);
	
  	
  	
  	
  	
  Assert.Fail();	
  	
  	
  	
  	
  	
  	
  	
  	
  
}	
  
23	
  
Тестирование	
  Rx	
  
[Test]	
  	
  
public	
  void	
  OnActivityWithinThreshold_DoNotLogout()	
  	
  
{
	
  	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  	
  var	
  manager	
  =	
  new	
  LogoutManager(TimeSpan.FromMinutes(5),	
  testScheduler);
	
  	
  	
  	
  	
  manager.Logout.Subscribe(_	
  =>	
  Assert.Fail());
	
  	
  	
  	
  	
  for	
  (var	
  i	
  =	
  0;	
  i	
  <	
  10;	
  i++)	
  	
  
	
  	
  	
  	
  	
  {
manager.UserActionsObserver.OnNext(Unit.Default);
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMinutes(3).Ticks);
}
}	
  
24	
  
Тестирование	
  Rx	
  
[Test]	
  	
  
public	
  void	
  OnActivityCommand_LogoutImmediately()	
  	
  	
  
{
	
  	
  	
  	
  	
  var	
  manager	
  =	
  new	
  LogoutManager(TimeSpan.FromMinutes(5));
	
  	
  	
  	
  	
  manager.Logout.Subscribe(_	
  =>	
  Assert.Pass());
	
  	
  	
  	
  	
  manager.LogoutCommandsObserver.OnNext(Unit.Default);
	
  	
  	
  	
  	
  Assert.Fail();	
  
}	
  
25	
  
Тестирование	
  Rx	
  
public	
  ReactiveCollection(Func<TItem,	
  string,	
  bool>	
  filterFunc)
:	
  this(filterFunc,	
  NewThreadScheduler.Default,	
  DispatcherScheduler.Current,	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  )	
  	
  {	
  	
  }
	
  
	
  	
  	
  	
  	
  public	
  ReactiveCollection(Func<TItem,	
  string,	
  bool>	
  filterFunc,	
  IScheduler	
  scheduler)	
  
	
  	
  	
  	
  	
  :	
  this(filterFunc,	
  scheduler,	
  scheduler,	
  scheduler)	
  {	
  }	
  	
  
	
  public	
  ReactiveCollection(Func<TItem,	
  string,	
  bool>	
  filterFunc,
	
  	
  	
  	
  	
  IScheduler	
  filterScheduler,	
  IScheduler	
  observeOn,	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  )	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  var	
  buffer	
  =	
  new	
  ObservableCollection<TItem>();
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  _subscription	
  =	
  this.PropertyChangedAsObservable(x	
  =>	
  x.Filter)
.Throttle(TimeSpan.FromMilliseconds(timeout),	
  timerScheduler)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(_	
  =>	
  string.IsNullOrWhiteSpace(Filter)	
  ||	
  Filter.Length	
  >	
  3)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .DistinctUntilChanged(_	
  =>	
  Filter)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Merge(this.PropertyChangedAsObservable(x	
  =>	
  x.Source))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Do(_	
  =>	
  buffer.Clear())
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Select(_	
  =>	
  Source.EmptyIfNull()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .ToObservable(filterScheduler)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Where(x	
  =>	
  filterFunc(x,	
  Filter))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Buffer(TimeSpan.FromMilliseconds(200),	
  timerScheduler)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Do(x	
  =>	
  x.ForEach(buffer.Add)))
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Switch()
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .ObserveOn(observeOn)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  .Subscribe(_	
  =>	
  View	
  =	
  buffer.ToArray());
	
  }
Scheduler.Default	
  
IScheduler	
  timerScheduler	
  
26	
  
Тестирование	
  Rx	
  
[Test]
public	
  void	
  OnFilter_FilterView()
{
	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  var	
  collection	
  =	
  new	
  ReactiveCollection<string>(	
  
	
  (x,	
  y)	
  =>	
  x.Contains(y),	
  testScheduler)
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  Source	
  =	
  new[]	
  {"client",	
  "bad	
  client",	
  "item",	
  "second	
  item"},
	
  	
  	
  	
  };
	
  	
  	
  	
  	
  collection.Filter	
  =	
  "item";
	
  	
  	
  	
  	
  testScheduler.Start();
	
  	
  	
  	
  	
  Assert.IsTrue(new[]	
  {"item",	
  "second	
  item"}.SequenceEqual(collection.View));
}	
  
27	
  
Тестирование	
  Rx	
  
[Test]
public	
  void	
  OnFilter_RunFiltrationInAnotherThread()
{
	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  var	
  filterThreadId	
  =	
  0;
	
  	
  	
  	
  new	
  ReactiveCollection<int>((x,	
  y)	
  =>
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  	
  filterThreadId	
  =	
  Thread.CurrentThread.ManagedThreadId;
	
  	
  	
  	
  	
  	
  	
  	
  	
  return	
  true;
	
  	
  	
  	
  },	
  NewThreadScheduler.Default,	
  testScheduler,	
  testScheduler)
	
  	
  	
  	
  collection.Source	
  =	
  new	
  int[10];	
  
	
  
	
  	
  	
  	
  testScheduler.Start();
	
  	
  	
  	
  Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId,	
  filterThreadId);
}	
  
28	
  
Тестирование	
  Rx	
  
[Test]
public	
  void	
  OnSlowFilter_BufferResults()
{
	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  var	
  filterCounter	
  =	
  0;
	
  	
  	
  	
  var	
  collection	
  =	
  new	
  ReactiveCollection<int>((x,	
  y)	
  =>
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  if	
  (++filterCounter%3	
  ==	
  0)
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  testScheduler.Sleep(TimeSpan.FromMilliseconds(250).Ticks);
	
  	
  	
  	
  	
  	
  	
  	
  return	
  true;
	
  	
  	
  	
  },	
  testScheduler)
	
  	
  	
  	
  collection.Source	
  =	
  new	
  int[10];	
  
	
  
	
  	
  	
  	
  collection.PropertyChangedAsObservable(x	
  =>	
  x.View)
	
  	
  	
  	
  	
  	
  	
  	
  	
  .Take(3)
	
  	
  	
  	
  	
  	
  	
  	
  	
  .Subscribe(x	
  =>	
  Assert.IsTrue(collection.View.Length%3	
  ==	
  0));
	
  	
  	
  	
  testScheduler.Start();
	
  	
  	
  	
  Assert.AreEqual(10,	
  collection.View.Length);
}	
  
29	
  
Тестирование	
  Rx	
  
[Test]
public	
  void	
  OnSourceChanged_CallFilter_EvenWithinThreshold()
{
	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  var	
  filterCounter	
  =	
  0;
	
  	
  	
  	
  var	
  collection	
  =	
  new	
  ReactiveCollection<int>((x,	
  y)	
  =>
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  filterCounter++;
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  return	
  true;
	
  	
  	
  	
  },	
  testScheduler)
	
  
	
  	
  	
  	
  for	
  (var	
  i	
  =	
  0;	
  i	
  <	
  10;	
  i++)
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  collection.Source	
  =	
  new	
  int[1];
	
  	
  	
  	
  	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(100).Ticks);
	
  	
  	
  	
  }	
  
	
  	
  	
  	
  Assert.AreEqual(10,	
  filterCounter);
}	
  
30	
  
Тестирование	
  Rx	
  
[Test]
public	
  void	
  OnSameFilter_DoNotCallFilter()
{
	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  var	
  filterCounter	
  =	
  0;
	
  	
  	
  	
  var	
  collection	
  =	
  new	
  ReactiveCollection<int>((x,	
  y)	
  =>
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  filterCounter++;
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  return	
  true;
	
  	
  	
  	
  },	
  testScheduler)
	
  	
  	
  	
  collection.Source	
  =	
  new	
  int[1];	
  
	
  	
  	
  	
  collection.Filter	
  =	
  "firstFilter";	
  
	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(250).Ticks);	
  
	
  
	
  	
  	
  	
  filterCounter	
  =	
  0;	
  
	
  	
  	
  	
  for	
  (var	
  i	
  =	
  0;	
  i	
  <	
  10;	
  i++)
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  collection.Filter	
  =	
  "secondFilter";
	
  	
  	
  	
  	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(150).Ticks);
	
  	
  	
  	
  	
  	
  	
  	
  collection.Filter	
  =	
  "firstFilter";
	
  	
  	
  	
  	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(250).Ticks);
	
  	
  	
  	
  }
	
  	
  	
  	
  Assert.AreEqual(0,	
  filterCounter);
}	
  
31	
  
Тестирование	
  Rx	
  [TestCase(100,	
  false)]
[TestCase(150,	
  false)]
[TestCase(250,	
  true)]
[TestCase(500,	
  true)]
public	
  void	
  OnMultipleChangesWithinThreshold_DoNotCallFilter(	
  
	
   	
   	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  int	
  threshold,	
  bool	
  runFilter)
{
	
  	
  	
  	
  var	
  testScheduler	
  =	
  new	
  TestScheduler();
	
  	
  	
  	
  var	
  filterCounter	
  =	
  0;
	
  	
  	
  	
  var	
  collection	
  =	
  new	
  ReactiveCollection<int>((x,	
  y)	
  =>
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  filterCounter++;
	
  	
  	
  	
  	
  	
  	
  	
  return	
  true;
	
  	
  	
  	
  },	
  testScheduler)	
  {Source	
  =	
  new	
  int[1]};	
  
	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMinutes(1).Ticks);	
  
	
  
	
  	
  	
  	
  filterCounter	
  =	
  0;
	
  	
  	
  	
  for	
  (var	
  i	
  =	
  0;	
  i	
  <	
  10;	
  i++)
	
  	
  	
  	
  {
	
  	
  	
  	
  	
  	
  	
  	
  collection.Filter	
  +=	
  "filter";
	
  	
  	
  	
  	
  	
  	
  	
  testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(threshold).Ticks);
	
  	
  	
  	
  }	
  
	
  	
  	
  	
  if	
  (runFilter)	
  
	
  	
  	
  	
  	
  	
  	
  	
  Assert.AreNotEqual(0,	
  filterCounter);	
  
	
  	
  	
  	
  else	
  
	
  	
  	
  	
  	
  	
  	
  	
  Assert.AreEqual(0,	
  filterCounter);	
  
}	
   32	
  

More Related Content

PPTX
Stream API: рекомендации лучших собаководов
tvaleev
 
PDF
Thread
Alexander Rusin
 
PDF
RxJava+RxAndroid (Lecture 20 – rx java)
Noveo
 
PPTX
Мифы и легенды Java Stream API
CEE-SEC(R)
 
PDF
Асинхронный JavaScript
Александр Рудевич
 
PDF
2.4 Использование указателей
DEVTYPE
 
PDF
RDSDataSource: Promises
RAMBLER&Co
 
PDF
Очень вкусный фрукт Guava
Egor Chernyshev
 
Stream API: рекомендации лучших собаководов
tvaleev
 
RxJava+RxAndroid (Lecture 20 – rx java)
Noveo
 
Мифы и легенды Java Stream API
CEE-SEC(R)
 
Асинхронный JavaScript
Александр Рудевич
 
2.4 Использование указателей
DEVTYPE
 
RDSDataSource: Promises
RAMBLER&Co
 
Очень вкусный фрукт Guava
Egor Chernyshev
 

What's hot (20)

PDF
Интуит. Разработка приложений для iOS. Лекция 8. Работа с данными
Глеб Тарасов
 
PDF
Apache spark
Anton Anokhin
 
PPTX
Multiprocessor Programming Intro (lecture 3)
Dmitry Tsitelov
 
PDF
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Mail.ru Group
 
PDF
Yield at me 'cause I'm awaiting: асинхронные итераторы в C# 8
Andrey Karpov
 
PPTX
Developing highload servers with Java
Andrei Pangin
 
ODP
Контейнеры и хранение объектов в ООП
itclub_kz
 
PPTX
Владимир Горбенко «Использование блоков в Objective-C»
e-Legion
 
PPT
9. java lecture library
MERA_school
 
PPT
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Python Meetup
 
PDF
Lec 3
Alexander Rusin
 
PDF
Swift School #2
Sergey Pronin
 
PDF
Java осень 2014 занятие 7
Technopark
 
PPTX
C#. От основ к эффективному коду
Vasiliy Deynega
 
PDF
Something about Golang
Anton Arhipov
 
PPTX
Григорий Демченко, Универсальный адаптер
Sergey Platonov
 
PPTX
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)
Ontico
 
PPTX
Svitla .Net meetup in Kiev, Anzhiiak Oleksii
Svitla Systems Inc.
 
PDF
Java осень 2013 лекция 2
Technopark
 
PPTX
Programming Java - Lection 06 - Multithreading - Lavrentyev Fedor
Fedor Lavrentyev
 
Интуит. Разработка приложений для iOS. Лекция 8. Работа с данными
Глеб Тарасов
 
Apache spark
Anton Anokhin
 
Multiprocessor Programming Intro (lecture 3)
Dmitry Tsitelov
 
Как не сделать врагами архитектуру и оптимизацию, Кирилл Березин, Mail.ru Group
Mail.ru Group
 
Yield at me 'cause I'm awaiting: асинхронные итераторы в C# 8
Andrey Karpov
 
Developing highload servers with Java
Andrei Pangin
 
Контейнеры и хранение объектов в ООП
itclub_kz
 
Владимир Горбенко «Использование блоков в Objective-C»
e-Legion
 
9. java lecture library
MERA_school
 
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Python Meetup
 
Swift School #2
Sergey Pronin
 
Java осень 2014 занятие 7
Technopark
 
C#. От основ к эффективному коду
Vasiliy Deynega
 
Something about Golang
Anton Arhipov
 
Григорий Демченко, Универсальный адаптер
Sergey Platonov
 
Хранимые процедуры в NoSQL СУБД на примере Tarantool / Денис Линник (Mail.Ru)
Ontico
 
Svitla .Net meetup in Kiev, Anzhiiak Oleksii
Svitla Systems Inc.
 
Java осень 2013 лекция 2
Technopark
 
Programming Java - Lection 06 - Multithreading - Lavrentyev Fedor
Fedor Lavrentyev
 
Ad

Viewers also liked (10)

PPTX
Конструкторы.Деструкторы.Динамическое распределение памяти
REX-MDK
 
PPTX
Интерфейсы
REX-MDK
 
PPTX
Делегаты
REX-MDK
 
PPTX
Обобщенные классы в C#
REX-MDK
 
PDF
Anemic Domain Model - антипаттерн или SOLID?
GoSharp
 
PDF
TPL Dataflow – зачем и для кого?
GoSharp
 
PPTX
Основы Java. 4. Collection Framework
Sergey Nemchinsky
 
PDF
ReSharper: прошлое и будущее
GoSharp
 
PDF
Программируем быстрее с CodeRush
GoSharp
 
PDF
Как попасть на следующий уровень карьеры и зарплаты в C#
GoSharp
 
Конструкторы.Деструкторы.Динамическое распределение памяти
REX-MDK
 
Интерфейсы
REX-MDK
 
Делегаты
REX-MDK
 
Обобщенные классы в C#
REX-MDK
 
Anemic Domain Model - антипаттерн или SOLID?
GoSharp
 
TPL Dataflow – зачем и для кого?
GoSharp
 
Основы Java. 4. Collection Framework
Sergey Nemchinsky
 
ReSharper: прошлое и будущее
GoSharp
 
Программируем быстрее с CodeRush
GoSharp
 
Как попасть на следующий уровень карьеры и зарплаты в C#
GoSharp
 
Ad

Similar to Живые приложения с Rx (20)

PPTX
Как приручить реактивное программирование в XAML приложениях
Denis Tsvettsih
 
PPTX
Reactive Extensions
GetDev.NET
 
PPTX
Как приручить реактивное программирование
Denis Tsvettsih
 
PPTX
Reactive extensions
Sergey Teplyakov
 
PPT
CodeFest 2013. Никонов Г. — Как мы разрабатываем приложения для Windows Phone...
CodeFest
 
PPTX
Приложения для Windows Phone: как мы это делаем #codefest
Actis Wunderman
 
PPTX
Высокоуровневая обработка событий
Bonart
 
PDF
Как приручить реактивное программирование
DotNetConf
 
PDF
Быстрое построение backendов c помощью реактивных потоков
CodeFest
 
PPTX
Объять необъятное, или как использовать несколько MVVM фреймворков в одном XA...
Denis Tsvettsih
 
PPTX
Solit 2014, Реактивный Javascript. Победа над асинхронностью и вложенностью, ...
solit
 
PPTX
Rx
Juri Mulenko
 
PDF
ReactiveUI: Rx + MVVM
Stas Shusha
 
PDF
Codefest-2015 Reactive Streams
Alexey Romanchuk
 
PDF
Reactive programming для успеха вашего стартапа
Vitebsk DSC
 
PPTX
Reactive UI на C#
Nikolay Yasinskiy
 
PDF
Rx android
GDG Odessa
 
PDF
Что нам стоит DAL построить? Акуляков Артём D2D Just.NET
Dev2Dev
 
PPTX
PostSharp - Threading Model Library
Andrey Gordienkov
 
PDF
PostSharp - Threading Model
Andrey Gordienkov
 
Как приручить реактивное программирование в XAML приложениях
Denis Tsvettsih
 
Reactive Extensions
GetDev.NET
 
Как приручить реактивное программирование
Denis Tsvettsih
 
Reactive extensions
Sergey Teplyakov
 
CodeFest 2013. Никонов Г. — Как мы разрабатываем приложения для Windows Phone...
CodeFest
 
Приложения для Windows Phone: как мы это делаем #codefest
Actis Wunderman
 
Высокоуровневая обработка событий
Bonart
 
Как приручить реактивное программирование
DotNetConf
 
Быстрое построение backendов c помощью реактивных потоков
CodeFest
 
Объять необъятное, или как использовать несколько MVVM фреймворков в одном XA...
Denis Tsvettsih
 
Solit 2014, Реактивный Javascript. Победа над асинхронностью и вложенностью, ...
solit
 
ReactiveUI: Rx + MVVM
Stas Shusha
 
Codefest-2015 Reactive Streams
Alexey Romanchuk
 
Reactive programming для успеха вашего стартапа
Vitebsk DSC
 
Reactive UI на C#
Nikolay Yasinskiy
 
Rx android
GDG Odessa
 
Что нам стоит DAL построить? Акуляков Артём D2D Just.NET
Dev2Dev
 
PostSharp - Threading Model Library
Andrey Gordienkov
 
PostSharp - Threading Model
Andrey Gordienkov
 

More from GoSharp (20)

PDF
Эволюция пользовательского интерфейса бизнес-приложений: от DOSa через окна в...
GoSharp
 
PDF
UniversalApp "убийца" WPF или же это WPF+ ?
GoSharp
 
PDF
UI тестирование WPF приложений в Дойче Банке
GoSharp
 
PDF
Практика применения Enterprise Architect и T4-шаблонов для разработки системы...
GoSharp
 
PDF
За что не любить EF и чем его заменить
GoSharp
 
PDF
MVVM в WinForms – DevExpress Way (теория и практика)
GoSharp
 
PDF
Паттерны быстрой разработки WPF MVVM бизнес-приложений
GoSharp
 
PPTX
Gosharp Intro
GoSharp
 
PDF
Проектирование сетевой инфраструктуры под SOA проекты ASP.NET
GoSharp
 
PDF
Мониторинг приложений ASP.NET на основе сервиса Application Insights
GoSharp
 
PDF
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET
GoSharp
 
PDF
ASP.NET Internals
GoSharp
 
PDF
Кросплатформенная разработка на ASP.NET vNext
GoSharp
 
PDF
Внедрение зависимостей в ASP.NET MVС и ASP.NET vNext
GoSharp
 
PDF
Будущее ASP.NET
GoSharp
 
PDF
Коучинг команд разработки и коучинговые инструменты в работе тимлида
GoSharp
 
PDF
Взаимное влияние Source Code Management и других средств организации разработки
GoSharp
 
PDF
DevOPS инструменты для .NET проектов
GoSharp
 
PDF
Доски проектов и продуктов на TFS: Agile-визуализация на уровне компании
GoSharp
 
PDF
Архитектурные решения при создании облачного сервиса на Asp.Net
GoSharp
 
Эволюция пользовательского интерфейса бизнес-приложений: от DOSa через окна в...
GoSharp
 
UniversalApp "убийца" WPF или же это WPF+ ?
GoSharp
 
UI тестирование WPF приложений в Дойче Банке
GoSharp
 
Практика применения Enterprise Architect и T4-шаблонов для разработки системы...
GoSharp
 
За что не любить EF и чем его заменить
GoSharp
 
MVVM в WinForms – DevExpress Way (теория и практика)
GoSharp
 
Паттерны быстрой разработки WPF MVVM бизнес-приложений
GoSharp
 
Gosharp Intro
GoSharp
 
Проектирование сетевой инфраструктуры под SOA проекты ASP.NET
GoSharp
 
Мониторинг приложений ASP.NET на основе сервиса Application Insights
GoSharp
 
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET
GoSharp
 
ASP.NET Internals
GoSharp
 
Кросплатформенная разработка на ASP.NET vNext
GoSharp
 
Внедрение зависимостей в ASP.NET MVС и ASP.NET vNext
GoSharp
 
Будущее ASP.NET
GoSharp
 
Коучинг команд разработки и коучинговые инструменты в работе тимлида
GoSharp
 
Взаимное влияние Source Code Management и других средств организации разработки
GoSharp
 
DevOPS инструменты для .NET проектов
GoSharp
 
Доски проектов и продуктов на TFS: Agile-визуализация на уровне компании
GoSharp
 
Архитектурные решения при создании облачного сервиса на Asp.Net
GoSharp
 

Живые приложения с Rx

  • 2. Почему  Reac,ve  Extensions?   •  Отзывчивость  приложений   •  Упрощение  кода  работы  со  временем   •  Упрощение  управления  потоками   •  Упрощение  подписки-­‐отписки  для  событий   •  Тестируемость  кода   •  LINQ  для  событий  и  потоков  данных   •  Наличие  реализаций  для  других  языков   программирования   2  
  • 4. Преимущества  Rx  перед  событиями   •  События  однопоточны.  Все  обработчики  будут  вызываться  по  очереди   в  потоке  который  запустил  событие.   •  Если  в  одном  из  обработчиков  события  будет  выброшено  исключение   все  остальные  обработчики  не  будут  вызваны   •  Все  подписки  на  события  необходимо  отписывать.  Для  этого  придется   хранить  ссылку  на  метод  для  отписки  и  объект   •  Rx  поддерживает  async/await  для  запросов  требующих  ожидания   конкретного  элемента   •  Rx  предоставляет  методы  для  управления  временем  и  потоками,  а   также  тестовые  реализации  управляющих  объектов.     •  Rx  предоставляет  методы  для  связи  нескольких  потоков  данных  в   один   •  Rx  предоставляет  возможность  обрабатывать  данные  аналогично   LINQ-­‐запросу  в  функциональном  стиле.   4  
  • 5. Основные  интерфейсы  Rx   •  IObservable<T>        IDisposable  Subscribe(IObserver<T>  observer);   •  IObserver<T>    void  OnNext(T  value);    void  OnError(Exception  error);    void  OnCompleted();   •  IScheduler   •  ISubject<T>  :  IObservable<T>, IObserver<T> 5  
  • 6. Простой  класс  с  Rx          public  class  LogoutManager          {                  public  IObservable<Unit>  Logout  {  get;  private  set;  }                  public  IObserver<Unit>  UserActionsObserver  {  get;  private  set;  }                  public  IObserver<Unit>  LogoutCommandsObserver  {  get;  private  set;  }                      public  LogoutManager(TimeSpan  timeout)                  {                          var  userActionsSubject  =  new  Subject<Unit>();                          var  logoutSubject  =  new  Subject<Unit>();                            UserActionsObserver  =  userActionsSubject.AsObserver();                          LogoutCommandsObserver  =  logoutSubject.AsObserver();                            Logout  =  userActionsSubject                                  .StartWith(Unit.Default)                                  .Throttle(timeout)                                  .Merge(logoutSubject);                  }          }   6  
  • 7. Потоки  данных  в  LogoutManager    Logout  =  userActionsSubject                            .StartWith(Unit.Default)                            .Throttle(timeout)                            .Merge(logoutSubject);   7  
  • 8. Интеграция  Rx  с  существующими   событиями   •  Короткая:   Observable.FromEventPattern(source,  "PropertyChanged");   •  Типобезопасная:   Observable.FromEventPattern   <PropertyChangedEventHandler,PropertyChangedEventArgs>(              handler  =>  source.PropertyChanged  +=  handler,                handler  =>  source.PropertyChanged  -­‐=  handler)   •  Промежуточные  варианты  с  указанием  типов  только   для  части  операндов       8  
  • 10. Текстовый  фильтр  на  Rx    Observable.FromEventPattern   <TextChangedEventHandler,  TextChangedEventArgs>(    handler  =>  filterTextBox.TextChanged  +=  handler,    handler  =>  filterTextBox.TextChanged  -­‐=  handler)            .Select(x  =>  x.Sender)            .Cast<TextBox>()            .Select(x  =>  x.Text)            .Where(x  =>  x.Length  >  2)      .Subscribe(FilterCollection);   10  
  • 11. Расширенный  фильтр  на  Rx   •  Исходная  коллекция  также  может  меняться   •  Пока  пользователь  продолжает  ввод  не  имеет  смысла   перезапускать  поиск   •  Логика  фильтрации  может  быть  достаточно  сложна  и   отнимать  много  времени   •  Пользователь  должен  видеть  результаты  по  мере  их   поступления,  а  не  после  завершения  фильтрации   •  Пользователь  может  начать  вводить  фильтр,  а  затем   вернуть  старый.  Повторную  фильтрацию  при  этом  не   надо  запускать   11  
  • 12. Расширенный  фильтр  на  Rx    public  interface  IReactiveCollection<TItem>  :            INotifyPropertyChanged,  IDisposable {    //  Источник  данных  (полная  коллекция)                  TItem[]  Source  {  get;  set;  }                    //  Отображаемая  часть  данных  (отфильтрованная  коллекция)                  TItem[]  View  {  get;  }                    //  Строка  фильтра                  string  Filter  {  get;  set;  }              }   12  
  • 13. Расширенный  фильтр  на  Rx   public  ReactiveCollection(Func<TItem,  string,  bool>  filterFunc)  {          var  buffer  =  new  ObservableCollection<TItem>();            _subscription  =  this.PropertyChangedAsObservable(x  =>  x.Filter)                    .Throttle(TimeSpan.FromMilliseconds(200))                    .Where(_  =>  string.IsNullOrWhiteSpace(Filter)  ||  Filter.Length  >  2)                    .DistinctUntilChanged(_  =>  Filter)                    .Merge(this.PropertyChangedAsObservable(x  =>  x.Source))                    .Do(_  =>  buffer.Clear())                    .Select(_  =>  Source.EmptyIfNull()                            .ToObservable()                            .Where(x  =>  filterFunc(x,  Filter))                            .Buffer(TimeSpan.FromMilliseconds(200))                            .Do(x  =>  x.ForEach(buffer.Add)))                    .Switch()                    .Subscribe(_  =>  View  =  buffer.ToArray());   } 13  
  • 14. Потоки  данных  в  расширенном   фильтре   this.PropertyChangedAsObservable(x  =>  x.Filter)                    .Throttle(TimeSpan.FromMilliseconds(200))                    .Where(_  =>  string.IsNullOrWhiteSpace(Filter)  ||  Filter.Length  >  2)                    .DistinctUntilChanged(_  =>  Filter)                    .Merge(this.PropertyChangedAsObservable(x  =>  x.Source)) 14  
  • 15. Потоки  данных  в  расширенном   фильтре      .Select(_  =>  Source.EmptyIfNull()                            .ToObservable()                            .Where(x  =>  filterFunc(x,  Filter))                            .Buffer(TimeSpan.FromMilliseconds(200))                            .Do(x  =>  x.ForEach(buffer.Add)))                  .Switch()   15  
  • 16. Управление  потоками  в  Rx   public  ReactiveCollection(Func<TItem,  string,  bool>  filterFunc,                                                                                          )  {          var  buffer  =  new  ObservableCollection<TItem>();            _subscription  =  this.PropertyChangedAsObservable(x  =>  x.Filter)                    .Throttle(TimeSpan.FromMilliseconds(timeout))                    .Where(_  =>  string.IsNullOrWhiteSpace(Filter)  ||  Filter.Length  >  3)                    .DistinctUntilChanged(_  =>  Filter)                    .Merge(this.PropertyChangedAsObservable(x  =>  x.Source))                    .Do(_  =>  buffer.Clear())                    .Select(_  =>  Source.EmptyIfNull()                            .ToObservable(                              )                            .Where(x  =>  filterFunc(x,  Filter))                            .Buffer(TimeSpan.FromMilliseconds(timeout))                            .Do(x  =>  x.ForEach(buffer.Add)))                    .Switch()                      .ObserveOn(observeOn)                    .Subscribe(_  =>  View  =  buffer.ToArray());   } IScheduler  filterScheduler,  IScheduler  observeOn     filterScheduler   16  
  • 18. Проблемы  реализации  при  помощи   событий   •  Необходимость  реализации  задержки  для   событий  –  для  того,  чтобы  не  загружать  списки   клиентов  и  счетов  много  раз   •  Постоянные  подписки  и  отписки  на  события   элементов  коллекции  клиентов,  отсутствие   отписок  быстро  приведет  к  утечке  памяти  в   приложении.   •  Код  для  управления  потоками  –  фильтрация  в   отдельном  потоке,  вывод  результатов  в  другом   18  
  • 19. Применение  фильтра  на  Rx   Companies  =  new  ReactiveCollection<Company>((x,  y)  =>                                                          string.IsNullOrWhiteSpace(y)  ||  x.Name.Contains(y))  {          Source  =  CompanyList        };      Clients  =  new  ReactiveCollection<Client>((x,  y)  =>                    string.IsNullOrWhiteSpace(y)  ||  x.Name.Contains(y));    Accounts  =  new  ReactiveCollection<Account>(SlowFilter);    private  bool  SlowFilter(Account  x,  string  y)  {    Thread.Sleep(random.Next(50,  250));    return  string.IsNullOrWhiteSpace(y)  ||  x.Number.ToString().Contains(y);  }   19  
  • 20. Применение  фильтра  на  Rx    Companies.Source.Select(x  =>  x.PropertyChangedAsObservable(y  =>  y.Selected))            .Merge()            .Throttle(TimeSpan.FromMilliseconds(200))            .ObserveOnDispatcher()            .Subscribe(_  =>  Clients.Source  =  GetClients(Companies.Source                          .Where(x  =>  x.Selected)                          .Select(x  =>  x.Id)));      Clients.PropertyChangedAsObservable(x  =>  x.Source)        .Select(_  =>  Clients.Source)        .Select(x  =>  x.Select(y  =>  y.PropertyChangedAsObservable(z  =>  z.Selected))                    .Merge()                    .Throttle(TimeSpan.FromMilliseconds(100)))        .Switch()        .ObserveOnDispatcher()        .Subscribe(_  =>  Accounts.Source  =  GetAccountSource(Clients.Source                        .Where(x  =>  x.Selected)                        .Select(x  =>  x.Id)));   20  
  • 21. А  что  с  тестированием?   •  Управление  временем   •  Управление  потоками   •  Буферизация     •  Скорость  выполнения  тестов   21  
  • 22. Тестирование  Rx  при  помощи   интерфейса  IScheduler          public  class  LogoutManager          {                  public  IObservable<Unit>  Logout  {  get;  private  set;  }                  public  IObserver<Unit>  UserActionsObserver  {  get;  private  set;  }                  public  IObserver<Unit>  LogoutCommandsObserver  {  get;  private  set;  }                      public  LogoutManager(TimeSpan  timeout,                                            )                  {                          var  userActionsSubject  =  new  Subject<Unit>();                          var  logoutSubject  =  new  Subject<Unit>();                            UserActionsObserver  =  userActionsSubject.AsObserver();                          LogoutCommandsObserver  =  logoutSubject.AsObserver();                            Logout  =  userActionsSubject.StartWith(Unit.Default)                                  .Throttle(timeout,                    )                                  .Merge(logoutSubject);                  }                public  LogoutManager(TimeSpan  timeout)  :  this(timeout,  Scheduler.Default)    {  }          }    IScheduler  scheduler   scheduler   22  
  • 23. Тестирование  Rx   [Test]     public  void  OnInactivity_Logout()     {          var  testScheduler  =  new  TestScheduler();          var  manager  =  new  LogoutManager(TimeSpan.FromMinutes(5),  testScheduler);          manager.Logout.Subscribe(_  =>  Assert.Pass());          testScheduler.AdvanceBy(TimeSpan.FromMinutes(6).Ticks);          Assert.Fail();                   }   23  
  • 24. Тестирование  Rx   [Test]     public  void  OnActivityWithinThreshold_DoNotLogout()     {          var  testScheduler  =  new  TestScheduler();          var  manager  =  new  LogoutManager(TimeSpan.FromMinutes(5),  testScheduler);          manager.Logout.Subscribe(_  =>  Assert.Fail());          for  (var  i  =  0;  i  <  10;  i++)              { manager.UserActionsObserver.OnNext(Unit.Default);                    testScheduler.AdvanceBy(TimeSpan.FromMinutes(3).Ticks); } }   24  
  • 25. Тестирование  Rx   [Test]     public  void  OnActivityCommand_LogoutImmediately()       {          var  manager  =  new  LogoutManager(TimeSpan.FromMinutes(5));          manager.Logout.Subscribe(_  =>  Assert.Pass());          manager.LogoutCommandsObserver.OnNext(Unit.Default);          Assert.Fail();   }   25  
  • 26. Тестирование  Rx   public  ReactiveCollection(Func<TItem,  string,  bool>  filterFunc) :  this(filterFunc,  NewThreadScheduler.Default,  DispatcherScheduler.Current,                                                  )    {    }            public  ReactiveCollection(Func<TItem,  string,  bool>  filterFunc,  IScheduler  scheduler)            :  this(filterFunc,  scheduler,  scheduler,  scheduler)  {  }      public  ReactiveCollection(Func<TItem,  string,  bool>  filterFunc,          IScheduler  filterScheduler,  IScheduler  observeOn,                                                                          )  {                              var  buffer  =  new  ObservableCollection<TItem>();                            _subscription  =  this.PropertyChangedAsObservable(x  =>  x.Filter) .Throttle(TimeSpan.FromMilliseconds(timeout),  timerScheduler)                                        .Where(_  =>  string.IsNullOrWhiteSpace(Filter)  ||  Filter.Length  >  3)                                        .DistinctUntilChanged(_  =>  Filter)                                        .Merge(this.PropertyChangedAsObservable(x  =>  x.Source))                                        .Do(_  =>  buffer.Clear())                                        .Select(_  =>  Source.EmptyIfNull()                                                            .ToObservable(filterScheduler)                                                            .Where(x  =>  filterFunc(x,  Filter))                                          .Buffer(TimeSpan.FromMilliseconds(200),  timerScheduler)                                                            .Do(x  =>  x.ForEach(buffer.Add)))                                        .Switch()                                        .ObserveOn(observeOn)                                        .Subscribe(_  =>  View  =  buffer.ToArray());  } Scheduler.Default   IScheduler  timerScheduler   26  
  • 27. Тестирование  Rx   [Test] public  void  OnFilter_FilterView() {        var  testScheduler  =  new  TestScheduler();        var  collection  =  new  ReactiveCollection<string>(    (x,  y)  =>  x.Contains(y),  testScheduler)        {                                Source  =  new[]  {"client",  "bad  client",  "item",  "second  item"},        };          collection.Filter  =  "item";          testScheduler.Start();          Assert.IsTrue(new[]  {"item",  "second  item"}.SequenceEqual(collection.View)); }   27  
  • 28. Тестирование  Rx   [Test] public  void  OnFilter_RunFiltrationInAnotherThread() {        var  testScheduler  =  new  TestScheduler();        var  filterThreadId  =  0;        new  ReactiveCollection<int>((x,  y)  =>        {                  filterThreadId  =  Thread.CurrentThread.ManagedThreadId;                  return  true;        },  NewThreadScheduler.Default,  testScheduler,  testScheduler)        collection.Source  =  new  int[10];            testScheduler.Start();        Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId,  filterThreadId); }   28  
  • 29. Тестирование  Rx   [Test] public  void  OnSlowFilter_BufferResults() {        var  testScheduler  =  new  TestScheduler();        var  filterCounter  =  0;        var  collection  =  new  ReactiveCollection<int>((x,  y)  =>        {                if  (++filterCounter%3  ==  0)                        testScheduler.Sleep(TimeSpan.FromMilliseconds(250).Ticks);                return  true;        },  testScheduler)        collection.Source  =  new  int[10];            collection.PropertyChangedAsObservable(x  =>  x.View)                  .Take(3)                  .Subscribe(x  =>  Assert.IsTrue(collection.View.Length%3  ==  0));        testScheduler.Start();        Assert.AreEqual(10,  collection.View.Length); }   29  
  • 30. Тестирование  Rx   [Test] public  void  OnSourceChanged_CallFilter_EvenWithinThreshold() {        var  testScheduler  =  new  TestScheduler();        var  filterCounter  =  0;        var  collection  =  new  ReactiveCollection<int>((x,  y)  =>        {                filterCounter++;                      return  true;        },  testScheduler)          for  (var  i  =  0;  i  <  10;  i++)        {                collection.Source  =  new  int[1];                testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(100).Ticks);        }          Assert.AreEqual(10,  filterCounter); }   30  
  • 31. Тестирование  Rx   [Test] public  void  OnSameFilter_DoNotCallFilter() {        var  testScheduler  =  new  TestScheduler();        var  filterCounter  =  0;        var  collection  =  new  ReactiveCollection<int>((x,  y)  =>        {                filterCounter++;                      return  true;        },  testScheduler)        collection.Source  =  new  int[1];          collection.Filter  =  "firstFilter";          testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(250).Ticks);            filterCounter  =  0;          for  (var  i  =  0;  i  <  10;  i++)        {                collection.Filter  =  "secondFilter";                testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(150).Ticks);                collection.Filter  =  "firstFilter";                testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(250).Ticks);        }        Assert.AreEqual(0,  filterCounter); }   31  
  • 32. Тестирование  Rx  [TestCase(100,  false)] [TestCase(150,  false)] [TestCase(250,  true)] [TestCase(500,  true)] public  void  OnMultipleChangesWithinThreshold_DoNotCallFilter(                                                int  threshold,  bool  runFilter) {        var  testScheduler  =  new  TestScheduler();        var  filterCounter  =  0;        var  collection  =  new  ReactiveCollection<int>((x,  y)  =>        {                filterCounter++;                return  true;        },  testScheduler)  {Source  =  new  int[1]};          testScheduler.AdvanceBy(TimeSpan.FromMinutes(1).Ticks);            filterCounter  =  0;        for  (var  i  =  0;  i  <  10;  i++)        {                collection.Filter  +=  "filter";                testScheduler.AdvanceBy(TimeSpan.FromMilliseconds(threshold).Ticks);        }          if  (runFilter)                  Assert.AreNotEqual(0,  filterCounter);          else                  Assert.AreEqual(0,  filterCounter);   }   32