Sergiy Grytsenko, Senior Software Engineer
“Reactive Extensions: classic Observer in .NET”
• Why should we use Rx when we have events?
• Key types & methods
• Lifetime management, flow control
• Combining several streams
• Tests, I need unit tests!
2. About presenter
Senior Developer @ EPAM Systems
17 years of programing with Basic, Pascal, Assembler, C/C++, C#
12 years of professional development
Developed several projects using Rx
3. Target auditory
UI developers that deals with asynchronous code/want responsive UI
Back-end developers that wish to make server async and responsive
You just curious about the IObservable<T> and IObserver<T>
4. Agenda
Why use Rx when we have events?
Key types & methods
Lifetime management & flow control
Combining several streams
Scheduling and Threading
Tests, I need unit tests!
6. Why Rx?
You demand fire-and-forget messaging
You want to have the result pushed to you when it is ready
You do not want wait for entire set to be processed before you see first one
Developers have tools to push data
Developers need tools to react to push data
Rx enables developers to solve problems in an elegant, familiar and
declarative style with less code
It is easily to manage events and subscriptions
16. Lifetime management & flow control
Subscription lifetime
There is many Subscribe overloads but no Unsubscribe method
Rx returns IDisposable whenever subscription takes place
Or you can provide CancellationToken to unsubscribe
Think of IDisposable as subscription token
You can call Subscribe many times on single IObservable
17. Lifetime management & flow control
Observable lifetime
Both OnError & OnCompleted signify the completion of a stream
No futher calls to OnNext can be performed
When stream completes or errors, you should still dispose subscription
18. Lifetime management & flow control
Flow control
OnError publishes exception, not thows
Subscribe w/o OnError handler causes exception to throw
19. Lifetime management & flow control
Visualization
Stream that publishes 3 values and then completes
Stream that publishes 4 values and then errors
26. Scheduling and Threading
effectively remove the need for WaitHandles, and any explicit calls to
using Threads, the ThreadPool and the new shiny Task type
27. Scheduling and Threading
The invocation of the subscription
The publishing of notifications
public static class Observable
{
public static IObservable<TSource> ObserveOn<TSource>(
this IObservable<TSource> source, IScheduler scheduler)
{...}
public static IObservable<TSource> SubscribeOn<TSource>(
this IObservable<TSource> source, IScheduler scheduler)
{...}
}
28. Scheduling and Threading
public interface IScheduler
{
IDisposable Schedule(Action action);
IDisposable Schedule(Action action, TimeSpan dueTime);
DateTimeOffset Now { get; }
}
31. Tests, I need unit tests!
Scheduling and therefore Threading are generally avoided in test scenarios
as they can introduce race conditions which may lead to non-deterministic
tests.
Tests should run as fast as possible.
Rx is a new technology/library so naturally as we master it, we will refactor
our code. We want to use to tests to ensure our refactoring have not
altered the internal behavior of our code base.
32. Tests, I need unit tests!
TestScheduler
A virtual scheduler to allow us emulate and control time.
var scheduler = new TestScheduler();
var wasExecuted = false;
scheduler.Schedule(() => wasExecuted = true);
Assert.IsFalse(wasExecuted);
scheduler.AdvanceTo(1); //execute 1 tick of queued actions
Assert.IsTrue(wasExecuted);
33. Tests, I need unit tests!
TestScheduler
var scheduler = new TestScheduler();
var dueTime = TimeSpan.FromMilliseconds(400);
var delta = TimeSpan.FromMilliseconds(100);
scheduler.Schedule(dueTime, () => Console.WriteLine("1"));
scheduler.Schedule(dueTime, () => Console.WriteLine("2"));
scheduler.Schedule(dueTime.Add(delta), () => Console.WriteLine("3"));
scheduler.Schedule(dueTime.Add(delta), () => Console.WriteLine("4"));
Console.WriteLine("RunTo(dueTime)");
scheduler.AdvanceTo(dueTime.Ticks);
Console.WriteLine("Run()");
scheduler.Start();
/* Output:
RunTo(dueTime)
1
2
Run()
3
4
*/
LINQ integrated.
You can unite .NET event, an async method call, a Task<T> or 3rd party API into single paradigm.
You can write your own query operators/extension methods.
Declaration of what your code does and leaves the how to the operators.
Queries can be composed together to further produce composite queries.
Query can transform from one type to another, translate one value to another, aggregate sequence or expand single value to a sequence of values.
UI events like mouse move/click/down/up, button click or other control’s events
Domain events like property change, collection update or business logic events
Infrastructure events from FileWatcher, system or WMI
Integration events: broadcast from bessage bus, push event from WebSockets API or other low latency middleware
Create - Creates a subject from the specified observer and observable.
Subject - Represents an object that is both an observable sequence as well as an observer. Each notification is broadcasted to all subscribed observers.
AsyncSubject - Represents the result of an asynchronous operation. The last value before the OnCompleted notification, or the error received through OnError, is sent to all subscribed observers.
BehaviorSubject - Represents a value that changes over time. Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications.
ReplaySubject - Each notification is broadcasted to all subscribed and future observers, subject to buffer trimming policies. (Remembers all publications)
Materialize flatten 3 types of publications to stream of Notification<T>
Dematerialize converts stream of notifications to IObservable<T>
Do executes provided action and returns same observable
Run returns void and it is a blocking call
Concat – passes first then (when first completes) passes second
Amb – passes values from stream that first produce value
Merge – merges two or more streams passes by all values
SelectMany – provides Cartesian product of two streams
Zip – takes two values from both streams and returns one
CombineLatest – combines latest two values from both streams in time
ForkJoin – produces the one last combination of two values