Need some help with RX. I want define observable, which should create resource when first subscribtion created, post this resource instance once for every new subscribtion, and when all subscribtion are done that resource instance must be disposed. Something like Observable.Using, but with Publish(value) and RefCount behaviour. All my attempts to express it using standard operators failed. This code does what i want, but I think there must be standart way to do it. I'm really don't want reinvent the wheel.
using System;
using System.Reactive.Linq;
using System.Reactive.Disposables;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
// this part is what i can't express in standart RX operators..
Res res = null;
RefCountDisposable disp = null;
var #using = Observable.Create<Res>(obs =>
{
res = res ?? new Res();
disp = disp == null || disp.IsDisposed ? new RefCountDisposable(res) : disp;
obs.OnNext(res);
return new CompositeDisposable(disp.GetDisposable(), disp, Disposable.Create(() => res = null));
});
// end
var sub1 = #using.Subscribe(Print);
var sub2 = #using.Subscribe(Print);
sub1.Dispose();
sub2.Dispose();
sub1 = #using.Subscribe(Print);
sub2 = #using.Subscribe(Print);
sub1.Dispose();
sub2.Dispose();
Console.ReadKey();
}
static void Print(object o)
{
Console.WriteLine(o.GetHashCode());
}
}
class Res : IDisposable
{
public Res()
{
Console.WriteLine("CREATED");
}
public void Dispose()
{
Console.WriteLine("DISPOSED");
}
}
}
Output:
CREATED
1111
1111
DISPOSED
CREATED
2222
2222
DISPOSED
My "best" attempt with standart operators:
var #using = Observable.Using(() => new Res(), res => Observable.Never(res).StartWith(res))
.Replay(1)
.RefCount();
and output is:
CREATED
1111
1111
DISPOSED
CREATED
1111 <-- this is "wrong" value
2222
2222
DISPOSED
Thank you!
ps. sorry for my poor english =(
After a little headache i'm finally realized that problem with Using.Replay.RefCount was that Replay internally calls Multicast with single ReplaySubject instance, but in my specific case I need Replay that recreates subject on every new first subscription. Through google I found RXX library and it's ReconnectableObservable was the answer. It uses subject factory instead of subject instance to recreate subject in every Connect call(original rxx code, simply without contracts):
internal sealed class ReconnectableObservable<TSource, TResult> : IConnectableObservable<TResult>
{
private ISubject<TSource, TResult> Subject
{
get { return _subject ?? (_subject = _factory()); }
}
private readonly object _gate = new object();
private readonly IObservable<TSource> _source;
private readonly Func<ISubject<TSource, TResult>> _factory;
private ISubject<TSource, TResult> _subject;
private IDisposable _subscription;
public ReconnectableObservable(IObservable<TSource> source, Func<ISubject<TSource, TResult>> factory)
{
_source = source;
_factory = factory;
}
public IDisposable Connect()
{
lock (_gate)
{
if (_subscription != null)
return _subscription;
_subscription = new CompositeDisposable(
_source.Subscribe(Subject),
Disposable.Create(() =>
{
lock (_gate)
{
_subscription = null;
_subject = null;
}
}));
return _subscription;
}
}
public IDisposable Subscribe(IObserver<TResult> observer)
{
lock (_gate)
{
return Subject.Subscribe(observer);
}
}
}
and few extension methods:
public static class Ext
{
public static IConnectableObservable<T> Multicast<T>(this IObservable<T> obs, Func<ISubject<T>> subjectFactory)
{
return new ReconnectableObservable<T, T>(obs, subjectFactory);
}
public static IConnectableObservable<T> ReplayReconnect<T>(this IObservable<T> obs, int replayCount)
{
return obs.Multicast(() => new ReplaySubject<T>(replayCount));
}
public static IConnectableObservable<T> PublishReconnect<T>(this IObservable<T> obs)
{
return obs.Multicast(() => new Subject<T>());
}
}
using that code, now i'm able to do so:
var #using = Observable
.Using(() => new Res(), _ => Observable.Never(_).StartWith(_))
.ReplayReconnect(1) // <-- that's it!
.RefCount();
Yahoo! It works as expected.
Thanks for all who answered! You pushed me in the right direction.
Try this:
var #using = Observable.Using(
() => new Res(),
res => Observable.Return(res).Concat(Observable.Never<Res>()))
.Publish((Res)null)
.RefCount()
.SkipWhile(res => res == null);
The Concat prevents observers from auto-unsubscribing when the observable produces its only value.
If there is a way to do this using standard operators, I can't see it.
The problem is that there is no "cache values only if there are subscribers" option amonst the standard operators.
The Replay operator will cache the last value regardless of subscribers, and is the underlying cause of the "wrong" value you are seeing.
It highlights the fact that Using + Replay is a dangerous combination since it's emitted a disposed value.
I suspect that if someone did manage some wizardry with the standard operators, it wouldn't be as readable as your Observable.Create implementation.
I have used Observable.Create many times to create code that I am certain is more concise, readable and maintainable than an equivalent construction using standard operators.
My advice is that there is absolutely nothing wrong with using Observable.Create - wrap your code up in a nice factory method that accepts the resource and you're good to go. Here's my attempt at doing that, it's just a repackage of your code with thread safety added:
public static IObservable<T> CreateObservableRefCountedResource<T>(Func<T> resourceFactory)
where T : class, IDisposable
{
T resource = null;
RefCountDisposable resourceDisposable = null;
var gate = new object();
return Observable.Create<T>(o =>
{
lock (gate)
{
resource = resource ?? resourceFactory();
var disposeAction = Disposable.Create(() =>
{
lock (gate)
{
resource.Dispose();
resource = null;
}
});
resourceDisposable = (resourceDisposable == null || resourceDisposable.IsDisposed)
? new RefCountDisposable(disposeAction)
: resourceDisposable;
o.OnNext(resource);
return new CompositeDisposable(
resourceDisposable,
resourceDisposable.GetDisposable());
}
});
}
EDITED - Forgot to call resourceDisposable.GetDisposable()!
Related
As a caveat I'm a novice with Rx (2 weeks) and have been experimenting with using Rx, RxUI and Roland Pheasant's DynamicData.
I have a service that initially loads data from local persistence and then, upon some user (or system) instruction will contact the server (TriggerServer in the example) to get additional or replacement data. The solution I've come up with uses a Subject and I've come across many a site discussing the pros/cons of using them. Although I understand the basics of hot/cold it's all based on reading rather than real world.
So, using the below as a simplified version, is this 'right' way of going about this problem or is there something I haven't properly understood somewhere?
NB: I'm not sure how important it is, but the actual code is taken from a Xamarin.Forms app, that uses RxUI, the user input being a ReactiveCommand.
Example:
using DynamicData;
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
public class MyService : IDisposable
{
private CompositeDisposable _cleanup;
private Subject<Unit> _serverSubject = new Subject<Unit>();
public MyService()
{
var data = Initialise().Publish();
AllData = data.AsObservableCache();
_cleanup = new CompositeDisposable(AllData, data.Connect());
}
public IObservableCache<MyData, Guid> AllData { get; }
public void TriggerServer()
{
// This is what I'm not sure about...
_serverSubject.OnNext(Unit.Default);
}
private IObservable<IChangeSet<MyData, Guid>> Initialise()
{
return ObservableChangeSet.Create<MyData, Guid>(async cache =>
{
// inital load - is this okay?
cache.AddOrUpdate(await LoadLocalData());
// is this a valid way of doing this?
var sync = _serverSubject.Select(_ => GetDataFromServer())
.Subscribe(async task =>
{
var data = await task.ConfigureAwait(false);
cache.AddOrUpdate(data);
});
return new CompositeDisposable(sync);
}, d=> d.Id);
}
private IObservable<MyData> LoadLocalData()
{
return Observable.Timer(TimeSpan.FromSeconds(3)).Select(_ => new MyData("localdata"));
}
private async Task<MyData> GetDataFromServer()
{
await Task.Delay(2000).ConfigureAwait(true);
return new MyData("serverdata");
}
public void Dispose()
{
_cleanup?.Dispose();
}
}
public class MyData
{
public MyData(string value)
{
Value = value;
}
public Guid Id { get; } = Guid.NewGuid();
public string Value { get; set; }
}
And a simple Console app to run:
public static class TestProgram
{
public static void Main()
{
var service = new MyService();
service.AllData.Connect()
.Bind(out var myData)
.Subscribe(_=> Console.WriteLine("data in"), ()=> Console.WriteLine("COMPLETE"));
while (Continue())
{
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine($"Triggering Server Call, current data is: {string.Join(", ", myData.Select(x=> x.Value))}");
service.TriggerServer();
}
}
private static bool Continue()
{
Console.WriteLine("Press any key to call server, x to exit");
var key = Console.ReadKey();
return key.Key != ConsoleKey.X;
}
}
Looks very good for first try with Rx
I would suggest few changes:
1) Remove the Initialize() call from the constructor and make it a public method - helps a lot with unit tests and now you can await it if you need to
public static void Main()
{
var service = new MyService();
service.Initialize();
2) Add Throttle to you trigger - this fixes parallel calls to the server returning the same results
3) Don't do anything that can throw in Subscribe, use Do instead:
var sync = _serverSubject
.Throttle(Timespan.FromSeconds(0.5), RxApp.TaskPoolScheduler) // you can pass a scheduler via arguments, or use TestScheduler in unit tests to make time pass faster
.Do(async _ =>
{
var data = await GetDataFromServer().ConfigureAwait(false); // I just think this is more readable, your way was also correct
cache.AddOrUpdate(data);
})
// .Retry(); // or anything alese to handle failures
.Subscribe();
I'm putting what I've come to as my solution just in case there's others that find this while they're wandering the internets.
I ended up removing the Subjects all together and chaining together several SourceCache, so when one changed it pushed into the other and so on. I've removed some code for brevity:
public class MyService : IDisposable
{
private SourceCache<MyData, Guid> _localCache = new SourceCache<MyData, Guid>(x=> x.Id);
private SourceCache<MyData, Guid> _serverCache = new SourceCache<MyData, Guid>(x=> x.Id);
public MyService()
{
var localdata = _localCache.Connect();
var serverdata = _serverCache.Connect();
var alldata = localdata.Merge(serverdata);
AllData = alldata.AsObservableCache();
}
public IObservableCache<MyData, Guid> AllData { get; }
public IObservable<Unit> TriggerLocal()
{
return LoadLocalAsync().ToObservable();
}
public IObservable<Unit> TriggerServer()
{
return LoadServerAsync().ToObservable();
}
}
EDIT: I've changed this again to remove any chaining of caches - I just manage the one cache internally. Lesson is not to post too early.
Subject.HasObservers is not immediately true in the sample code attached for an undetermined number of ticks. If I take out the SubscribeOn(), HasObservers is always true, so I know it's to do with IScheduler initialization.
This was causing a problem in our production software where the first few calls to OnNext() were going nowhere despite a guarantee that the IDisposable subscription variable had been initialized before the thread that called OnNext() was allowed to proceed. Is this a bug in RX?
What are other ways to use System.Reactive classes to guarantee the subscription is setup with a scheduler without polling?
I have tried Subject.Synchronize(), but that made no difference.
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
var source = new Subject<long>();
IDisposable subscription = source
.SubscribeOn(ThreadPoolScheduler.Instance)
.Subscribe(Console.WriteLine);
// 0 and 668,000 ticks for subscription setup, but rarely 0.
int iterations = 0;
while (!source.HasObservers)
{
iterations++;
Thread.SpinWait(1);
}
// Next line would rarely output to Console without while loop
source.OnNext(iterations);
subscription.Dispose();
source.Dispose();
}
}
I expected Subject.HasObservers to be true without polling.
As I understand, the problem is that your subscription is done asynchronously: the call is not blocked, so the real subscription will be done later on other thread.
I didn't find the exact way of knowing if the subscription has really landed (it might be even not possible at all). If your problem is the race between the first OnNext and subscription, than maybe you need to convert your Observable into a Connectable Observable using Replay() + Connect(). This way you'll ensure that every subscriber gets exactly the same sequence.
using (var source = new Subject<long>())
{
var connectableSource = source.Replay();
connectableSource.Connect();
using (var subscription = connectableSource
.SubscribeOn(ThreadPoolScheduler.Instance)
.Subscribe(Console.WriteLine))
{
source.OnNext(42); // outputs 42 always
Console.ReadKey(false);
}
}
In my code I still need Console.ReadKey because of the race between subscription done on the other thread and unsubscription.
The solution I came up with for now that I'm hoping someone can improve upon:
public class SubscribedSubject<T> : ISubject<T>, IDisposable
{
private readonly Subject<T> _subject = new Subject<T>();
private readonly ManualResetEventSlim _subscribed = new ManualResetEventSlim();
public bool HasObservers => _subject.HasObservers;
public void Dispose() => _subject.Dispose();
public void OnCompleted() => Wait().OnCompleted();
public void OnError(Exception error) => Wait().OnError(error);
public void OnNext(T value) => Wait().OnNext(value);
public IDisposable Subscribe(IObserver<T> observer)
{
IDisposable disposable = _subject.Subscribe(observer);
_subscribed.Set();
return disposable;
}
private Subject<T> Wait()
{
_subscribed.Wait();
return _subject;
}
}
Example use:
using (var source = new SubscribedSubject<long>())
{
using (source
.SubscribeOn(ThreadPoolScheduler.Instance)
.Subscribe(Console.WriteLine))
{
source.OnNext(42);
Console.ReadKey();
}
}
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'm slowly starting to get the hang of unit-testing and mocking, but it's a slow process. I have tried unit testing this Active Directory code. The question is not strictly relevant to AD.
class ActiveDirectoryQueryer {
DirectorySearcher mSearcher;
public ActiveDirectoryQueryer() {
var searcher = new DirectorySearcher(...);
}
public void GetAllMailEntries() {
MailEntries =
mSearcher
.FindAll()
.Select(result => result.GetDirectoryEntry())
.Select(BuildNewADUser)
.ToList();
}
static ActiveDirectoryUser BuildNewADUser(DirectoryEntry pDirectoryEntry) {
return ActiveDirectoryUser.Create(
pDirectoryEntry.Guid,
(pDirectoryEntry.Properties["name"].Value ?? "").ToString(),
(pDirectoryEntry.Properties["mail"].Value ?? "").ToString()
);
}
So, I would like to unit test the GetAllMailEntries method. In order to do this using MOQ I've had to manually generate interfaces and wrappers for various .NET types, and changed many of the above references to interfaces instead (like IDirectoryEntry). Each of the IXxxx interfaces below has an associated wrapper class XxxxWrapper. In total I added at least 12 new source files just for this one test. Here's what I've ended up with for the unit test:
[TestMethod]
public void TestGetAllMailEntries() {
var mockSearcher = new Mock<IDirectorySearcher>();
var mockResultCollection = new Mock<ISearchResultCollection>();
var mockSearchResult = new Mock<ISearchResult>();
var mockDirectoryEntry = new Mock<IDirectoryEntry>();
var mockPropertyCollection = new Mock<IPropertyCollection>();
var nameMockPropertyValueCollection = new Mock<IPropertyValueCollection>();
var mailMockPropertyValueCollection = new Mock<IPropertyValueCollection>();
const string name = "SomeNameValue";
const string mailAddress = "SomeMailAddress";
nameMockPropertyValueCollection.SetupGet(pvc => pvc.Value).Returns(name);
mailMockPropertyValueCollection.SetupGet(pvc => pvc.Value).Returns(mailAddress);
mockPropertyCollection.SetupGet(pc => pc["name"]).Returns(nameMockPropertyValueCollection.Object);
mockPropertyCollection.SetupGet(pc => pc["mail"]).Returns(mailMockPropertyValueCollection.Object);
mockDirectoryEntry.SetupGet(de => de.Properties).Returns(mockPropertyCollection.Object);
mockSearchResult.Setup(sr => sr.GetDirectoryEntry()).Returns(mockDirectoryEntry.Object);
mockResultCollection.Setup(results => results.GetEnumerator()).Returns(new List<ISearchResult> { mockSearchResult.Object }.GetEnumerator());
mockSearcher.Setup(searcher => searcher.FindAll()).Returns(mockResultCollection.Object);
var queryer = new ActiveDirectoryQueryer(mockSearcher.Object);
queryer.GetAllMailEntries();
Assert.AreEqual(1, queryer.MailEntries.Count());
var entry = queryer.MailEntries.Single();
Assert.AreEqual(name, entry.Name);
Assert.AreEqual(mailAddress, entry.EmailAddress);
}
Is it normal to have this many interfaces and wrapper classes? (The wrappers are necessary since .NET types cannot otherwise implement my interfaces.)
I think my problem is mirroring the .NET structure too closely. I shouldn't wrap each and every .NET type all the way down till I get to just primitives. Rather, I should take the first opportunity to remove all dependencies as soon as I can. In this case it's with the DirectorySearcher class, and the FindAll method.
DirectorySearcher.FindAll returns a SearchResultCollection, but rather than thinking of my "wrapper" class as just an adapter to the .NET type, I should make more use of it.
Ignoring the implementation of IDisposable and other unnecessary code, my wrapper had looked like this:
public interface IDirectorySearcher : IDisposable {
ISearchResultCollection FindAll();
}
class DirectorySearcherWrapper : IDirectorySearcher {
DirectorySearcher mDirectorySearcher;
DirectorySearcherWrapper(DirectorySearcher pDirectorySearcher) {
mDirectorySearcher = pDirectorySearcher;
}
public static IDirectorySearcher Wrap(DirectorySearcher pDirectorySearcher) {
return new DirectorySearcherWrapper(pDirectorySearcher);
}
public ISearchResultCollection FindAll() {
return SearchResultCollectionWrapper.Wrap(mDirectorySearcher.FindAll());
}
}
Rather, I should take the opportunity to stop all dependencies right here. I don't have to return a .NET type or even just a wrapper to a .NET type, I can now use this interface to return whatever I want. IE: If what I want to get from the FindAll method is a bunch of ActiveDirectoryUsers, then return just that.
My code then becomes:
public interface IDirectorySearcher : IDisposable {
IEnumerable<ActiveDirectoryUser> FindAll();
}
class DirectorySearcherWrapper : IDirectorySearcher {
DirectorySearcher mDirectorySearcher;
DirectorySearcherWrapper(DirectorySearcher pDirectorySearcher) {
mDirectorySearcher = pDirectorySearcher;
}
public static IDirectorySearcher Wrap(DirectorySearcher pDirectorySearcher) {
return new DirectorySearcherWrapper(pDirectorySearcher);
}
public IEnumerable<ActiveDirectoryUser> FindAll() {
return
mDirectorySearcher
.FindAll()
.Cast<SearchResult>()
.Select(result => result.GetDirectoryEntry())
.Select(/*BuildNewADUser*/)
.ToList();
}
}
And the GetAllMailEntries method becomes simply:
public void GetAllMailEntries() {
MailEntries = mSearcher.FindAll();
}
And the unit test becomes:
[TestMethod]
public void TestGetAllMailEntries2() {
var mockSearcher = new Mock<IDirectorySearcher>();
mockSearcher
.Setup(s => s.FindAll())
.Returns(new[] {
ActiveDirectoryUser.Create(new Guid(), "Name", "EmailAddress")
});
var queryer = new ActiveDirectoryQueryer(mockSearcher.Object);
queryer.GetAllMailEntries();
Assert.AreEqual(1, queryer.MailEntries.Count());
var entry = queryer.MailEntries.Single();
Assert.AreEqual("Name", entry.Name);
Assert.AreEqual("EmailAddress", entry.EmailAddress);
}
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);
}