Sometimes, you want to limit the rate at which events arrive from an Rx stream.
Throttle operator will suppress an event if another arrives within a specified interval. This is very useful in many instances, but it does have two important side-effects – even an unsuppressed event will be delayed by the interval, and events will get dropped altogether if they arrive too quickly.
I came across a situation where both of these were unacceptable. In this particular case, the desired behaviour was as follows: The events should be output at a maximum rate specified by a TimeSpan, but otherwise as soon as possible.
One solution works like this. Imagine our input stream is a bunch of people arriving at a railway station. For our output, we want people leave the station at a maximum rate. We set the maximum rate by having each person stand at the front of a flatbed railroad truck and sending that truck out of the station at a fixed speed. Because there is only one track, and all trucks travel at the same speed and have the same length, people will leave the station at a maximum rate when trucks are departing back-to-back. However, if the track is clear, the next person will be able to depart immediately.
So how to we translate this metaphor into Rx?
We will use the
Concat operator’s ability to accept a stream of streams and merge them together back-to-back – just like sending railroad trucks down the track.
To get the equivalent of each person onto a railroad truck, we will use a Select to project each event (person) to an observable sequence (railroad truck) that starts with a single
OnNext event (the person) and ends with an
OnComplete exactly the defined interval later.
Lets assume the input events are an
IObservable<T> in the variable
input. Here’s the code:
var paced = input.Select(i => Observable.Empty<T>() .Delay(interval) .StartWith(i)).Concat();
I’ll leave it as an exercise to turn this into an extension method for easy reuse!