RX
Intro to Reactive Extensions
http://slides.com/yshayy/rx/live
Agenda
- What's RX?
- Why RX?
- Soluto use cases
ReactiveX
Originated originally by MS for handling events and
data-flows based on the Observer pattern and LINQ.
Open source, cross platform and multi-lingual.
Become more popular lately with the increasing
popularity of reactive and functional programming.
C# Hello rx
Observable.Interval(TimeSpan.FromSeconds(1))
.Select( x => x + ".Hello RX");
.Subscribe( message => Console.WriteLine(message));
Output:
1. Hello RX
2. Hello RX
3. Hello RX
...
ZIP method/operator
Taken from http://rxmarbles.com/
Those are linq methods, right?
RX building blocks
IObservable<T> - Represents a push based data
stream of values of type T. aka stream or publisher.
interface IObservable<T>{
IDisposable subscribe(IObserver<T> subscriber)
}
RX building blocks
IObserver<T> -> represent a subscriber for
incoming messages.
interface IObserver<T>
{
void OnNext(T item);
void OnError(Exception ex);
void OnComplete();
}
RX BUILDING BLOCKS
Very similar to Enumrable/Iterable, only push based
Event
|
Enumerable - pull
|
Observable - push
|
retrieve data |
MoveNext() Current |
OnNext( (T)=>void) |
discover error |
throws Exception |
OnError( (Exception)=>void) |
complete |
!MoveNext() |
OnCompleted(()=> void) |
Due to those similarities, we can use most
LINQ operators for manipulating observables
Produce value every second
|
Enumerable (Pull)
|
Observable (Push)
|
Consume
|
foreach (var x in GetEnumerable())
{
DoSomething(x)
}
|
GetObservable()
.Subscribe( x=> DoSomething(x))
|
Produce
|
IEnumerable<Guid> GetEnumerable() {
while (true)
{
Thread.sleep(1000);
yield return Guid.NewGuid();
}
}
|
IObservable<Guid> GetObservable() {
return Observable
.Interval(Timespan.FromSeconds(1))
.Select(x=> Guid.NewGuid());
}
|
Blocking vs. non blocking
Code - Auto suggest (NO RX)
Code - Auto suggest (NO RX)
Code - Auto suggest (NO RX)
Code - Auto suggest (NO RX)
Code - Auto suggest (NO RX)
DO you follow?
"My second remark is that our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively poorly developed. For that reason we should do (as wise programmers aware of our limitations) our utmost to shorten the conceptual gap between the static program and the dynamic process, to make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible."
By "Edsger+W.+Dijkstra" on "Go to considered harmful" paper
Or more simply put...
DO YOU FOLLOW?
Async is hard
Time is hard
State is hard
We are stupid
Plan accordingly
SWITCH (aka switchonnext)
The need to go reactive
- Application requirements have changed over the years -
- More users requesting data more rapidly
- Data is very large and come with different sources
and different shapes.
- Users expect their application to have
real time response.
- Stricter SLAs, High availability -
even in non mission critical applications.
THE NEED TO GO REACTIVE
- Offloading, multi-threading and asynchronous processing
can help solve those issue, but they can add real
complexity to the system and make it impossible to maintainable, and very difficult to scale.
THE NEED TO GO REACTIVE
In 2013, the first version of the reactive
manifesto was introduced.
It described the following traits of a reactive system:
- Responsiveness
- Resilient
- Scalable/Elastic
-
Event-Driven/Message-Driven
Reactive manifesto
REACTIVE MANIFESTO
Responsive -> react to users
System should be able to give response to users in solid and consistent time.
REACTIVE MANIFESTO
Resilient -> react to errors
System should be highly available in the face of failure.
REACTIVE MANIFESTO
Scalable/elastic -> react to load
System stay responsive and available under any load.
REACTIVE MANIFESTO
Message/Event driven -> react to events
System should use loosely coupled and isolated components.
Event driven system features
1. Asynchronous message passing ->
By passing events we make our system more :
decoupled - as components don't need to know each other.
scalable - no dependency on specific computation model .
2. Non blocking -> Means the system could make better utilization of resources, since inactive components release their resources.
.NET Event model
What's wrong with .net events?
- Concurrency -> .net events don't allow to make event dispatch on different thread.
- Completion -> since events don't have any signaling mechanism for ending, events are usually the source of many memory leaks.
- Composition -> it's very difficult to compose two or more .net events.
- All those effects error handling as well...
Event handling can be a pain
Adobe desktop apps 2008*
• 1/3 of the code in Adobe’s desktop applications is devoted
to event handling
• 1/2 of the bugs reported during a product cycle exist in
this code
http://infoscience.epfl.ch/record/176887/files/DeprecatingObservers2012.pdf?version=1
RX solution
- Extensions for creating and composing observables.
- LINQ Extensions for high lever data manipulation
- Scheduling services and extensions for handling concurrency
- .Net IObservable<T> and IObserver<T> implementations
Android example - Image Loading
Observable<Bitmap> imageObservable = Observable.create(observer -> {
Bitmap bm = downloadBitmap();
return bm;
});
imageObservable.subscribe(image -> loadToImageView(image));
But it will block the UI...
imageObservable
.subscribeOn(Schedulers.newThread())
.subscribe(image -> loadToImageView(image));
But it will not invoke loadImage on the right thread
imageObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(image -> loadToImageView(image));
Example taken from FRP on android: http://slides.com/yaroslavheriatovych/frponandroid/#/7/1Soluto
- Different roles handle different step in message processing
- Role to role communication is asynchronous
- Using queue, low(er) level abstraction
- All task based code is internally event-driven (async-await)
IObservable<T> and Task<T>
Similarities
- Can represent future event value.
- Allows to consume value in non-blocking way.
- Designed to help asynchronous services composition .
- Have support for different synchronization context.
- Help solving the "callback hell" problem
IOBSERVABLE<T> AND TASK<T>
|
Single return value |
Mutiple return values |
Pull/Synchronous
|
T
|
IEnumerable<T>
|
Push/Asynchronous
|
Task<T>
|
IObservable<T>
|
IOBSERVABLE<T> VS. TASK<T>
CAN PLAY TOGETHER!
- await can work on observable
- Task.ToObservable -> convert Task to observable
-
Observable.ToTask -> convert observable to Task
public async Task<int> GetNumberFromServiceWithTimeout()
{
return await someService.getNumber().ToObservable().Timeout(TimeSpan.FromSeconds(5))
}
Some examples in soluto
TableDataContext.GetObservable() ->
Allow us to do a table scan asynchronously and continuesoly
QueueDataContext.Read/ReadUntilEmpty() ->
Allow us to read queue asynchronously in a push style
Worker.Create ->
Built on top of QueueDataContext Rx Extensions,
handle throughput (backpressure), monitoring and handling errors
for queue worker. (still not complete tough...)
AnaylyticsDispatcher ->
Use RX IScheduler for creating dedicated dispatching loop for events
while maintaining Unit Testability and decoupling for computation model.
CacheConfigurationLoader ->
Get Observable in constructor injection which use for pushing invalidate cache messages.
IVR Controller
await queue.ReadUntilEmpty()
.Select(x => QueueTask.From(x, () => queue.DeleteMessage(x)))
.Select(x => x.AsString)
.SelectMany(x => mPiiStorageAccessor.GetPii<SomePiiData>(Guid.Parse(x)))
.Aggregate((prev, current) => prev + current)
Code is actually different, since we need to add some extensions that handle queue behavior. (deleting message after processing, moving message to error queue, etc...)
Worker example
return Worker.Create(mQueue, mErrorQueue, MonitoringObserver))
.Of(EntityTransformers.Container(mContainer))
.ProcessMessage(async message =>
{
await someHandler.ProcessMeessage(message);
});
Handling throughput and error handling
Cache Configuration Loader
public CachedConfigurationLoader(ILogger aLogger, IConfigurationLoader aLoader, IObservable<Unit> aInvalidate)
{
mLogger = aLogger;
mLoader = aLoader;
aInvalidate.Subscribe(
x => mConfigurationSetByCategory.Clear(),
ex => mLogger.Error(
"Configuration cache invalidator threw an exception - cache will not refresh",
ex);
}
And in DI module
var policy = Observable.Interval(TimeSpan.FromMinutes(5)).Select(_ => Unit.Default);
return new CachedConfigurationLoader(ctx.Resolve<ILogger>(), loader, policy);
Analytics Dispatcher
public AnalyticsDispatcher(IAnalyticsEnqueuer analyticsEnqueuer, AnalyticsModelCreator modelCreator, IScheduler scheduler = null)
{
mAnalyticsEnqueuer = analyticsEnqueuer;
mModelCreator = modelCreator;
mScheduler = scheduler ?? TaskPoolScheduler.Default;
}
And in DI Module:
builder.Register((ctx) => new EventLoopScheduler().Catch((Exception ex) =>
{
ctx.Resolve<ILogger>().Error("Unhandled error in AnalyticsDispatcher",ex,null,"AnaltyicsDispatcher");
return true;
}))
.Keyed<IScheduler>(SCHEDULER_KEY)
.SingleInstance();
Where to use?
For most async purposes, use Tasks by default!
Use RX Observables for:
- Time-based operations
- Async operations that produce multiple values.
- Ongoing push stream of data.
- Non .net environments without async-await or/and inferior "Future<T>" implementations.
- Better composition capabilities than offered by Task<T>
- Complex concurrency control.
- Lazy operations.
- UI, when not using other data-binding libraries.
IOBSERVABLE<T> VS. TASK<T>
Tasks
- Represent one future value.
- Simpler for most cases.
- Have better integration with async-await.
- Represent a running task, completed or canceled task, never idle one.
- Offer easier exception handling.
IOBSERVABLE<T> VS. TASK<T>
Observables
- Can receive any number of future values.
- Offer better composition and manipulation
based on LINQ/FP operators.
-
More suited for time based operations -
can replace all .net timers
- Provide more powerful scheduling abstraction
- Lazy by default
RX iS really big
This was just intro
RX is really big
There are 100+ RX operators for observables,
and there are many other concepts related.
RX is almost a programming paradigm rather then just library.
RX is really big
It's well suited for polygot programming
due to it's wide language and platform support.
RX is really big
Everytime you need to do things related to time,
concurrency, async processing, which feels too
complex, give RX a spin, or at least 10 minutes.