!!warning: Rx newbie!!
We have multiple price feeds. The requirement is to subscribe to all these feeds and only output the latest tick every 1 sec(throttle)
public static class FeedHandler
{
private static IObservable<PriceTick> _combinedPriceFeed = null;
private static double _throttleFrequency = 1000;
public static void AddToCombinedFeed(IObservable<PriceTick> feed)
{
_combinedPriceFeed = _combinedPriceFeed != null ? _combinedPriceFeed.Merge(feed) : feed;
AddFeed(_combinedPriceFeed);
}
private static IDisposable _subscriber;
private static void AddFeed(IObservable<PriceTick> feed)
{
_subscriber?.Dispose();
_subscriber = feed.Buffer(TimeSpan.FromMilliseconds(_throttleFrequency)).Subscribe(buffer => buffer.GroupBy(x => x.InstrumentId, (key, result) => result.First()).ToObservable().Subscribe(NotifyClient));
}
public static void NotifyClient(PriceTick tick)
{
//Do some action
}
}
The code have multiple issues. If I call AddToCombinedFeed with the same feed multiple times, the streams will get duplicated to start with. Eg. below
IObservable<PriceTick> feed1;
FeedHandler.AddToCombinedFeed(feed1);//1 stream
FeedHandler.AddToCombinedFeed(feed1);//2 streams(even though the groupby and first() functions will prevent this effect to propagate further
This brings me to the question. If I want to remove one price stream from the merged stream, how can I do that?
Update - New Solution
With Dynamic-Data (MIT-License) from RolandPheasant with Nuget.
Use a SourceList instead of a List
Use the MergeMany operator
Code:
public class FeedHandler
{
private readonly IDisposable _subscriber;
private readonly SourceList<IObservable<PriceTick>> _feeds = new SourceList<IObservable<PriceTick>>();
private readonly double _throttleFrequency = 1000;
public FeedHandler()
{
var combinedPriceFeed = _feeds.Connect().MergeMany(x => x).Buffer(TimeSpan.FromMilliseconds(_throttleFrequency)).SelectMany(buffer => buffer.GroupBy(x => x.InstrumentId, (key, result) => result.First()));
_subscriber = combinedPriceFeed.Subscribe(NotifyClient);
}
public void AddFeed(IObservable<PriceTick> feed) => _feeds.Add(feed);
public void NotifyClient(PriceTick tick)
{
//Do some action
}
}
Old Solution
Eradicate the need to resubscribe by applying Switch() technique.
Your _combinedPriceFeed just switches to the next observable that
will be supplied by _combinePriceFeedChange.
Keep a list to manage your multiple feeds. Create the new observable whenever the list changes and provide it via _combinePriceFeedChange.
You should get the logic of an corresponding remove method.
Code:
public class FeedHandler
{
private readonly IDisposable _subscriber;
private readonly IObservable<PriceTick> _combinedPriceFeed;
private readonly List<IObservable<PriceTick>> _feeds = new List<IObservable<PriceTick>>();
private readonly BehaviorSubject<IObservable<PriceTick>> _combinedPriceFeedChange = new BehaviorSubject<IObservable<PriceTick>>(Observable.Never<PriceTick>());
private readonly double _throttleFrequency = 1000;
public FeedHandler()
{
_combinedPriceFeed = _combinedPriceFeedChange.Switch().Buffer(TimeSpan.FromMilliseconds(_throttleFrequency)).SelectMany(buffer => buffer.GroupBy(x => x.InstrumentId, (key, result) => result.First()));
_subscriber = _combinedPriceFeed.Subscribe(NotifyClient);
}
public void AddFeed(IObservable<PriceTick> feed)
{
_feeds.Add(feed);
_combinedPriceFeedChange.OnNext(_feeds.Merge());
}
public void NotifyClient(PriceTick tick)
{
//Do some action
}
}
Here's the code you need:
private static SerialDisposable _subscriber = new SerialDisposable();
private static void AddFeed(IObservable<PriceTick> feed)
{
_subscriber.Disposable =
feed
.Buffer(TimeSpan.FromMilliseconds(_throttleFrequency))
.SelectMany(buffer =>
buffer
.GroupBy(x => x.InstrumentId, (key, result) => result.First()))
.Subscribe(NotifyClient);
}
Related
I have a requirement to connect to the server and collect data for processing. Below is my core class, which is responsible for looping through all the servers and try connecting them for processing.
public class CoreDataProcessingEngine : ICoreDataProcessingEngine
{
private readonly COMLib.ServerGateway _aServerGw;
private COMLib.ServerErrorInfo _aServerErrorInfo;
Public CoreDataProcessingEngine()
{
_aServerGw = new COMLib.ServerGateway();
_aServerErrorInfo = new COMLib.ServerErrorInfo();
}
//When service starts, I am collecting all the server details from config and trying to connect ONE BY ONE.
public async Task Start()
{
List<Server> servers = ConfigurationManager.GetSection("servers") as List<Server>;
foreach (var serverdetails in servers)
{
var data = Task.Run(() => ConnectToServer(serverdetails ));
}
}
}
Here is my ConnectToServer method
private async void ConnectToGateway(ServerDetails serverdetails )
{
await _aServerGw.connectToServerByName(serverdetails.serveraddress);
}
I have extended the connectToServerByName method as follow , which is in separate static class.
public static class ComLibraryExtensions
{
public static Task connectToServerByName(this ProxyGW #this, string serveraddress)
{
var tcs = new TaskCompletionSource<object>();
Action onSuccess = null;
Action<int> onFailed = null;
onSuccess = () =>
{
#this.onConnectSucceeded -= HandleManager_OnConnectSucceeded;
#this.onConnectFailed -= HandleManager_OnConnectFailed;
tcs.TrySetResult(null);
};
onFailed = hr =>
{
#this.onConnectSucceeded -= HandleManager_OnConnectSucceeded;
#this.onConnectFailed -= HandleManager_OnConnectFailed;
tcs.TrySetException(Marshal.GetExceptionForHR(hr));
};
#this.onConnectSucceeded += HandleManager_OnConnectSucceeded;
#this.onConnectFailed += HandleManager_OnConnectFailed;
#this.connectToGatewayByNameEx(serveraddress);
return tcs.Task;
}
private static void HandleManager_OnConnectFailed(int hr)
{
//How do I get access to dependent objects here?
//Like ILogger ??
_logger.Information(hr);
}
private static void HandleManager_OnConnectSucceeded()
{
//How do I get access #this??
#this.enableNotifications(true);//fails , it says #this does not exists
}
}
Question is:
How do I get access to _aServerGw in HandleManager_OnConnectSucceeded event, because I want to set some property based on the success event.
How do I get access to dependent objects here in extension classes like ILogger?
I've got a public static List<MyDoggie> DoggieList;
DoggieList is appended to and written to by multiple processes throughout my application.
We run into this exception pretty frequently:
Collection was modified; enumeration operation may not execute
Assuming there are multiple classes writing to DoggieList how do we get around this exception?
Please note that this design is not great, but at this point we need to quickly fix it in production.
How can we perform mutations to this list safely from multiple threads?
I understand we can do something like:
lock(lockObject)
{
DoggieList.AddRange(...)
}
But can we do this from multiple classes against the same DoggieList?
you can also create you own class and encapsulate locking thing in that only, you can try like as below ,
you can add method you want like addRange, Remove etc.
class MyList {
private object objLock = new object();
private List<int> list = new List<int>();
public void Add(int value) {
lock (objLock) {
list.Add(value);
}
}
public int Get(int index) {
int val = -1;
lock (objLock) {
val = list[0];
}
return val;
}
public void GetAll() {
List<int> retList = new List<int>();
lock (objLock) {
retList = new List<T>(list);
}
return retList;
}
}
Good stuff : Concurrent Collections very much in detail :http://www.albahari.com/threading/part5.aspx#_Concurrent_Collections
making use of concurrent collection ConcurrentBag Class can also resolve issue related to multiple thread update
Example
using System.Collections.Concurrent;
using System.Threading.Tasks;
public static class Program
{
public static void Main()
{
var items = new[] { "item1", "item2", "item3" };
var bag = new ConcurrentBag<string>();
Parallel.ForEach(items, bag.Add);
}
}
Using lock a the disadvantage of preventing concurrent readings.
An efficient solution which does not require changing the collection type is to use a ReaderWriterLockSlim
private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
With the following extension methods:
public static class ReaderWriterLockSlimExtensions
{
public static void ExecuteWrite(this ReaderWriterLockSlim aLock, Action action)
{
aLock.EnterWriteLock();
try
{
action();
}
finally
{
aLock.ExitWriteLock();
}
}
public static void ExecuteRead(this ReaderWriterLockSlim aLock, Action action)
{
aLock.EnterReadLock();
try
{
action();
}
finally
{
aLock.ExitReadLock();
}
}
}
which can be used the following way:
_lock.ExecuteWrite(() => DoggieList.Add(new Doggie()));
_lock.ExecuteRead(() =>
{
// safe iteration
foreach (MyDoggie item in DoggieList)
{
....
}
})
And finally if you want to build your own collection based on this:
public class SafeList<T>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly List<T> _list = new List<T>();
public T this[int index]
{
get
{
T result = default(T);
_lock.ExecuteRead(() => result = _list[index]);
return result;
}
}
public List<T> GetAll()
{
List<T> result = null;
_lock.ExecuteRead(() => result = _list.ToList());
return result;
}
public void ForEach(Action<T> action) =>
_lock.ExecuteRead(() => _list.ForEach(action));
public void Add(T item) => _lock.ExecuteWrite(() => _list.Add(item));
public void AddRange(IEnumerable<T> items) =>
_lock.ExecuteWrite(() => _list.AddRange(items));
}
This list is totally safe, multiple threads can add or get items in parallel without any concurrency issue. Additionally, multiple threads can get items in parallel without locking each other, it's only when writing than 1 single thread can work on the collection.
Note that this collection does not implement IEnumerable<T> because you could get an enumerator and forget to dispose it which would leave the list locked in read mode.
make DoggieList of type ConcurrentStack and then use pushRange method. It is thread safe.
using System.Collections.Concurrent;
var doggieList = new ConcurrentStack<MyDoggie>();
doggieList.PushRange(YourCode)
I have some RX code to test for inactivity on an event stream, activity on the stream resets an interval which is the inactivity trigger.
public interface IReportActivity
{
event EventHandler Activity;
}
public interface IInactivityMonitor
{
IObservable<Unit> ObserveInactivity(TimeSpan inactivityTimeout);
}
public class InactivityMonitor : IInactivityMonitor
{
private readonly ISchedulerProvider _schedulerProvider;
private readonly IReportActivity _activitySource;
private IObservable<Unit> _inactivityObservable;
public InactivityMonitor(IRaiseActivity activitySource, ISchedulerProvider schedulerProvider)
{
_activitySource = activitySource;
_schedulerProvider = schedulerProvider;
}
public IObservable<Unit> ObserveInactivity(TimeSpan inactivityTimeout)
{
return GetInactivityObservable()
.Select(_ => Observable.Interval(inactivityTimeout, _schedulerProvider.NewThread)
.Timestamp()
.Select(__ => Unit.Default))
.Switch();
}
public IObservable<Unit> GetInactivityObservable()
{
return _inactivityObservable = _inactivityObservable ??
Observable.FromEventPattern<EventHandler<EventArgs>, EventArgs>(
h => _activitySource.Activity += h,
h => _activitySource.Activity -= h)
.Sample(TimeSpan.FromSeconds(1), _schedulerProvider.NewThread)
.Select(_ => Unit.Default)
.Publish()
.RefCount();
}
}
The code works as required (although I think recreating the Interval every second is over kill - ObserveInactivity should Sample just before the timeout and reset based on the timestamp of the last activity)
The real issue I have is trying to test this code.
[TestFixture]
public class InactivityMonitorTests
{
private TestSchedulers _testSchedulers;
private InactivityMonitor _sut;
private AutoMock _moqqer;
protected override void Setup()
{
base.Setup();
_moqqer = new AutoMock()
_testSchedulers = new TestSchedulers();
_moqqer.Use<ISchedulerProvider>(_testSchedulers);
_sut = Moqqer.CreateInstance<InactivityMonitor>();
}
// this test passes
[Test]
public void GetInactivityObservable_ActivityDetected_ReportsActivity()
{
var activityObserved = false;
_sut.GetInactivityObservable()
.Subscribe(x => activityObserved = true);
RaiseActivityEvent();
_testSchedulers.NewThread.AdvanceBy(TimeSpan.FromSeconds(11).Ticks);
activityObserved.Should().BeTrue();
}
private void RaiseActivityEvent()
{
_moqqer.GetMock<IReportActivty>()
.Raise(m => m.Activity += null, EventArgs.Empty);
}
// this test fails, The interval never appears to get set up via the tests.
[Test]
public void ObserveActivity_InactivtyTimeoutExceeded_NotificationReceived()
{
var inactivityObserved = false;
_sut.ObserveInactivity(TimeSpan.FromSeconds(10))
.Subscribe(x => inactivityObserved = true);
_testSchedulers.NewThread.AdvanceBy(TimeSpan.FromSeconds(11).Ticks);
inactivityObserved.Should().BeTrue();
}
}
public interface ISchedulerProvider
{
IScheduler CurrentThread { get; }
IScheduler Dispatcher { get; }
IScheduler Immediate { get; }
IScheduler NewThread { get; }
IScheduler ThreadPool { get; }
IScheduler TaskPool { get; }
}
public sealed class TestSchedulers : ISchedulerProvider
{
private readonly TestScheduler _currentThread = new TestScheduler();
private readonly TestScheduler _dispatcher = new TestScheduler();
private readonly TestScheduler _immediate = new TestScheduler();
private readonly TestScheduler _newThread = new TestScheduler();
private readonly TestScheduler _threadPool = new TestScheduler();
private readonly TestScheduler _taskPool = new TestScheduler();
#region Explicit implementation of ISchedulerService
IScheduler ISchedulerProvider.CurrentThread { get { return _currentThread; } }
IScheduler ISchedulerProvider.Dispatcher { get { return _dispatcher; } }
IScheduler ISchedulerProvider.Immediate { get { return _immediate; } }
IScheduler ISchedulerProvider.NewThread { get { return _newThread; } }
IScheduler ISchedulerProvider.ThreadPool { get { return _threadPool; } }
IScheduler ISchedulerProvider.TaskPool { get { return _taskPool; } }
#endregion
public TestScheduler CurrentThread { get { return _currentThread; } }
public TestScheduler Dispatcher { get { return _dispatcher; } }
public TestScheduler Immediate { get { return _immediate; } }
public TestScheduler NewThread { get { return _newThread; } }
public TestScheduler ThreadPool { get { return _threadPool; } }
public TestScheduler TaskPool { get { return _taskPool; } }
}
I've tried various ways to start the scheduler, before subscribing and after, trying to subscribe on another scheduler and starting that before subscribe but no success.
EDIT: Debugging shows that the interval is never getting created in ObserveInactivity method.
Hoping someone can point me in the right direction.
Thanks
I would re-organise your inactivity query to so that instead of being triggered from _activitySource.Activity and then adding time to that, you instead expect failure/silence in XXX period and raise a notifcation for that. You then upgrade that query to cancel/restart if there was any activity.
i.e. instead of
public IObservable<Unit> GetInactivityObservable()
{
return _inactivityObservable = _inactivityObservable ??
Observable.FromEventPattern<EventHandler<EventArgs>, EventArgs>(
h => _activitySource.Activity += h,
h => _activitySource.Activity -= h)
.Sample(TimeSpan.FromSeconds(1), _schedulerProvider.NewThread)
.Select(_ => Unit.Default)
.Publish()
.RefCount();
}
change that around to
public IObservable<Unit> GetInactivityObservable()
{
return _inactivityObservable = _inactivityObservable ??
Observable.FromEventPattern<EventHandler<EventArgs>, EventArgs>(
h => _activitySource.Activity += h,
h => _activitySource.Activity -= h)
.Select(_=>Unit.Default)
.StartWith(Unit.Default)
.Sample(TimeSpan.FromSeconds(1), _schedulerProvider.NewThread)
.Select(a=>Observable.Timer(TimeSpan.FromSeconds(1), _schedulerProvider.NewThread).Select(_=>Unit.Default))
.Switch()
.Publish()
.RefCount();
}
And then, as Rx is already lazily evaluated, I would make this method static and non side-effecting and call it in your constructor, or just inline it.
public class InactivityMonitor : IInactivityMonitor
{
private readonly ISchedulerProvider _schedulerProvider;
private readonly IObservable<Unit> _inactivityObservable;
private static readonly TimeSpan SilencePeriod = TimeSpan.FromSeconds(1);
public InactivityMonitor(IRaiseActivity activitySource, ISchedulerProvider schedulerProvider)
{
_schedulerProvider = schedulerProvider;
_inactivityObservable = Observable.FromEventPattern<EventHandler<EventArgs>, EventArgs>(
h => _activitySource.Activity += h,
h => _activitySource.Activity -= h)
.StartWith(null)
.Select(a=>Observable.Timer(TimeSpan.FromSeconds(1), _schedulerProvider.NewThread))
.Switch()
.Select(_=>Unit.Default)
.Publish()
.RefCount();
}
//...
}
So, having a chance to get back to this problem I saw the error in my ways.
The Observable.Interval is never being started as no activity has been reported (by the misnamed inactivity observable). Running the app there is most likely at least 1 event raised. To fix this I added .StartWith(Unit.Default) as below, this allows the timeout to be started immediately for each observer which was obviously a potential bug in the original implementation.
public IObservable<Unit> ObserveInactivity(TimeSpan inactivityTimeout)
{
return GetInactivityObservable()
.StartWith(Unit.Default) // <--
.Select(_ => Observable.Interval(inactivityTimeout, _schedulerProvider.NewThread)
.Timestamp()
.Select(__ => Unit.Default))
.Switch();
}
So I have a server and I'm making calls to it through a wrapped up WebSocket (WebSocket4Net) and one of the requirements of the library I'm building is the ability to await on the return of the request. So I have a class MessageEventHandler that contains events that are triggered by the class MessageHandler as messages come in.
MessageEventHandler ex.
public class MessageEventHandler : IMessageEventHandler
{
public delegate void NodeNameReceived(string name);
public event Interfaces.NodeNameReceived OnNodeNameReceived;
public void NodeNameReceive(string name)
{
if (this.OnNodeNameReceived != null)
{
this.OnNodeNameReceived(name);
}
}
}
MessageHandler ex.
public class MessageHandler : IMessageHandler
{
private IMessageEventHandler eventHandler;
public MessageHandler(IMessageEventHandler eventHandler)
{
this.eventHandler = eventHandler;
}
public void ProcessDataCollectorMessage(string message)
{
var serviceMessage = JsonConvert.DeserializeObject<ServiceMessage>(message);
switch (message.MessageType)
{
case MessageType.GetNodeName:
{
var nodeName = serviceMessage.Data as string;
if (nodeName != null)
{
this.eventHandler.NodeNameReceive(nodeName);
}
break;
}
default:
{
throw new NotImplementedException();
}
}
}
Now building upon those classes I have the class containing my asynchronous function that handles the call to get the node name.
public class ClientServiceInterface : IClientServiceInterface
{
public delegate void RequestReady(ServiceMessage serviceMessage);
public event Interfaces.RequestReady OnRequestReady;
public int ResponseTimeout { get; private set; }
private IMessageEventHandler messageEventHandler;
public ClientServiceInterface(IMessageEventHandler messageEventHandler, int responseTimeout = 5000)
{
this.messageEventHandler = messageEventHandler;
this.ResponseTimeout = responseTimeout;
}
public Task<string> GetNodeNameAsync()
{
var taskCompletionSource = new TaskCompletionSource<string>();
var setHandler = default(NodeNameReceived);
setHandler = name =>
{
taskCompletionSource.SetResult(name);
this.messageEventHandler.OnNodeNameReceived -= setHandler;
};
this.messageEventHandler.OnNodeNameReceived += setHandler;
var ct = new CancellationTokenSource(this.ResponseTimeout);
var registration = new CancellationTokenRegistration();
registration = ct.Token.Register(
() =>
{
taskCompletionSource.TrySetCanceled();
this.messageEventHandler.OnNodeNameReceived -= setHandler;
registration.Dispose();
},
false);
var serviceMessage = new ServiceMessage() { Type = MessageType.GetNodeName };
this.ReadyMessage(serviceMessage);
return taskCompletionSource.Task;
}
}
As you can see I wouldn't call it pretty and I apologize if anyone threw up a little reading it. But this is my first attempt at wrapping a Task with Asynchronous Event. So with that on the table I could use some help.
Is there a better way to accomplish what I'm trying to achieve here? Remembering that I want a user of the library to either subscribe to the event and listen for all callbacks OR they can simply await the return depending on
their needs.
var nodeName = await GetNodeNameAsync();
Console.WriteLine(nodeName);
or
messageEventHandler.OnNodeNameReceived += (name) => Console.WriteLine(name);
GetNodeNameAsync();
Alternatively if my approach is actually 'good' can anyone provide any advice as to how I can write a helper function to abstract out setting up each function in this way? Any help would be greatly appreciated.
So I've written a couple classes to solve the problem I was having. The first of which is my CallbackHandle class which contains the task inside the TaskCompletionSource so each time that a request is made in my example a new callback handle is created.
public class CallbackHandle<T>
{
public CallbackHandle(int timeout)
{
this.TaskCompletionSource = new TaskCompletionSource<T>();
var cts = new CancellationTokenSource(timeout);
cts.Token.Register(
() =>
{
if (this.Cancelled != null)
{
this.Cancelled();
}
});
this.CancellationToken = cts;
}
public event Action Cancelled;
public CancellationTokenSource CancellationToken { get; private set; }
public TaskCompletionSource<T> TaskCompletionSource { get; private set; }
}
Then I have a 'handler' that manages the handles and their creation.
public class CallbackHandler<T>
{
private readonly IList<CallbackHandle<T>> callbackHandles;
private readonly object locker = new object();
public CallbackHandler()
{
this.callbackHandles = new List<CallbackHandle<T>>();
}
public CallbackHandle<T> AddCallback(int timeout)
{
var callback = new CallbackHandle<T>(timeout);
callback.Cancelled += () =>
{
this.callbackHandles.Remove(callback);
callback.TaskCompletionSource.TrySetResult("Error");
};
lock (this.locker)
{
this.callbackHandles.Add(callback);
}
return callback;
}
public void EventTriggered(T eventArgs)
{
lock (this.locker)
{
if (this.callbackHandles.Count > 0)
{
CallbackHandle<T> callback =
this.callbackHandles.First();
if (callback != null)
{
this.callbackHandles.Remove(callback);
callback.TaskCompletionSource.SetResult(eventArgs);
}
}
}
}
}
This is a simplified version of my actual implementation but it should get someone started if they need something similar. So to use this on my ClientServiceInterface class in my example I would start by creating a class level handler and using it like this:
public class ClientServiceInterface : IClientServiceInterface
{
private readonly CallbackHandler<string> getNodeNameHandler;
public ClientServiceInterface(IMessageEventHandler messageEventHandler, int responseTimeout = 5000)
{
this.messageEventHandler = messageEventHandler;
this.ResponseTimeout = responseTimeout;
this.getNodeNameHandler = new
CallbackHandler<string>();
this.messageEventHandler.OnNodeNameReceived += args => this.getNodeNameHandler.EventTriggered(args);
}
public Task<string> GetNodeNameAsync()
{
CallbackHandle<string> callbackHandle = this.getNodeNameHandler.AddCallback(this.ResponseTimeout);
var serviceMessage = new ServiceMessage
{
Type = MessageType.GetNodeName.ToString()
};
this.ReadyMessage(serviceMessage);
return callbackHandle.TaskCompletionSource.Task;
}
// Rest of class declaration removed for brevity
}
Which is much better looking than what I had before (at least in my opinion) and it's easy to extend.
For starters follow a thread-safe pattern:
public void NodeNameReceive(string name)
{
var evt = this.OnNodeNameReceived;
if (evt != null)
{
evt (name);
}
}
If you do not take a reference to the event object it can be set to null between the time you check null and call the method.
I have a stream with live data, and a stream which basically delimits parts of the live data that belong together. Now when someone subscribes to the live data stream, I would like to replay them the live data. However I don't want to remember all the live data, only the part since the last time the other stream emitted a value.
There is an issue which would solve my problem, since there is a replay operator which does exactly what I want (or at least I think).
What is currently the way to do this easily? Is there a better way than something like the following?
private class ReplayWithLimitObservable<TItem, TDelimiter> : IConnectableObservable<TItem>
{
private readonly List<TItem> cached = new List<TItem>();
private readonly IObservable<TDelimiter> delimitersObservable;
private readonly IObservable<TItem> itemsObservable;
public ReplayWithLimitObservable(IObservable<TItem> itemsObservable, IObservable<TDelimiter> delimitersObservable)
{
this.itemsObservable = itemsObservable;
this.delimitersObservable = delimitersObservable;
}
public IDisposable Subscribe(IObserver<TItem> observer)
{
lock (cached)
{
cached.ForEach(observer.OnNext);
}
return itemsObservable.Subscribe(observer);
}
public IDisposable Connect()
{
var delimiters = delimitersObservable.Subscribe(
p =>
{
lock (cached)
{
cached.Clear();
}
});
var items = itemsObservable.Subscribe(
p =>
{
lock (cached)
{
cached.Add(p);
}
});
return Disposable.Create(
() =>
{
items.Dispose();
delimiters.Dispose();
lock (cached)
{
cached.Clear();
}
});
}
public static IConnectableObservable<TItem> ReplayWithLimit<TItem, TDelimiter>(IObservable<TItem> items, IObservable<TDelimiter> delimiters)
{
return new ReplayWithLimitObservable<TItem, TDelimiter>(items, delimiters);
}
Does this do what you want? It has the advantage of leaving all of the locking and race conditions to the Rx pros :)
private class ReplayWithLimitObservable<T, TDelimiter> : IConnectableObservable<T>
{
private IConnectableObservable<IObservable<T>> _source;
public ReplayWithLimitObservable(IObservable<T> source, IObservable<TDelimiter> delimiter)
{
_source = source
.Window(delimiter) // new replay window on delimiter
.Select<IObservable<T>,IObservable<T>>(window =>
{
var replayWindow = window.Replay();
// immediately connect and start memorizing values
replayWindow.Connect();
return replayWindow;
})
.Replay(1); // remember the latest window
}
IDisposable Connect()
{
return _source.Connect();
}
IDisposable Subscribe(IObserver<T> observer)
{
return _source
.Concat()
.Subscribe(observer);
}
}
public static IConnectableObservable<TItem> ReplayWithLimit<TItem, TDelimiter>(IObservable<TItem> items, IObservable<TDelimiter> delimiters)
{
return new ReplayWithLimitObservable<TItem, TDelimiter>(items, delimiters);
}