Regular Rx Buffer(200)
emits data every 200 milliseconds. Let’s implement an operator which will emit data each 200 milliseconds or immediately if nothing was emitted in last interval. It’s like a traffic shaper which will give you data as soon as possible bot not more often than each 200 millis.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
using System; using System.Threading.Tasks; using System.Threading; using System.Reactive; using System.Reactive.Linq; using System.Collections.Generic; using System.Linq; using System.Reactive.Subjects; public class Program { public static void Main() { Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff") + " beginning"); var subject = new BehaviorSubject(0); subject.MagicBuffer(TimeSpan.FromMilliseconds(200)).Subscribe(x => Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff") + " Received " + string.Join(",", x))); // Should emit [0] immediately Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff") + " emitting 1,2,3"); subject.OnNext(1); // Should cache subject.OnNext(2); // Should cache subject.OnNext(3); // Should cache Thread.Sleep(250); // Should emit [1, 2, 3] Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff") + " should emit empty in a bit"); Thread.Sleep(250); // Should emit [] Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff") + " emitting 4"); subject.OnNext(4); // Should emit immediately subject.OnNext(5); // Should cache Thread.Sleep(200); } } public static class Ext{ public static IObservable<IList> MagicBuffer(this IObservable source, TimeSpan delay) { return Observable.Create<IList>(o => { DateTime lastEmitTime = DateTime.MinValue; DateTime lastCallTime = DateTime.MinValue; IList values = new List(); Task continuation = Task.CompletedTask; Action callback = _ => { lock(values){ if(DateTime.Now - lastCallTime >= delay){ lastCallTime = DateTime.Now; o.OnNext(values); values.Clear(); } } }; Action recur = null; recur = _ => Task.Delay(delay).ContinueWith(ignored => { continuation = continuation.ContinueWith(callback).ContinueWith(recur); }); recur(null); return source.Subscribe( v => { lock(values){ if(DateTime.Now - lastEmitTime >= delay){ lastEmitTime = lastCallTime = DateTime.Now; o.OnNext(new List{v}); recur(null); }else{ values.Add(v); } } }, o.OnError, o.OnCompleted ); }); } } |
Output:
1 2 3 4 5 6 7 8 9 |
06:47:07.311 beginning 06:47:07.374 Received 0 06:47:07.374 emitting 1,2,3 06:47:07.577 Received 1,2,3 06:47:07.640 should emit empty in a bit 06:47:07.780 Received 06:47:07.905 emitting 4 06:47:07.905 Received 4 06:47:08.108 Received 5 |
This is not optimal, there are probably too many continuations scheduled, also we lock directly instead of using some interlocked. However, seems like it’s working.