How to detect when NetworkStream finishes on Rx query - c#

I'm using Rx to read from a NetworkStream and provide the results as a Hot Observable.
Even if the query works great, I'm not sure if the condition to complete the sequence based on the NetworkStream is the most appropriate. I have cases where the sequence completes and the TcpListener on the other side has not finished or closed the connection.
Here is the query. I will appreciate to get some suggestions about the right condition to safety terminate the sequence:
private IDisposable GetStreamSubscription(TcpClient client)
{
return Observable.Defer(() => {
var buffer = new byte[client.ReceiveBufferSize];
return Observable.FromAsync<int>(() => {
return client.GetStream ().ReadAsync (buffer, 0, buffer.Length);
})
.SubscribeOn(NewThreadScheduler.Default)
.Select(x => buffer.Take(x).ToArray());
})
.Repeat()
.TakeWhile(bytes => bytes.Any()) //This is the condition to review
.Subscribe(bytes => {
//OnNext Logic
}, ex => {
//OnError logic
}, () => {
//OnCompleted Logic
});
}
Just to be clear about my question, I need to know the best way to detect when a Network Stream is completed on the other side (because of a disconnect, an error, or whatever). Right now I'm doing it by invoking ReadAsync until no bytes are returned, but I don't know if this is completely safe.

Does this do what you want?
private IDisposable GetStreamSubscription(TcpClient client)
{
return Observable
.Defer(() =>
{
var buffer = new byte[client.ReceiveBufferSize];
return Observable.Using(
() => client.GetStream(),
st => Observable.While(
() => st.DataAvailable,
Observable.Start(() =>
{
var bytes = st.Read(buffer, 0, buffer.Length);
return buffer.Take(bytes).ToArray();
})));
})
.SubscribeOn(NewThreadScheduler.Default)
.Subscribe(bytes => {
//OnNext Logic
}, ex => {
//OnError logic
}, () => {
//OnCompleted Logic
});
}
Note that this safely disposes of the stream and concludes when there is no longer any data available.

Related

RX.net - catching exceptions and higher order observables

I'm trying to learn RX(.net) and I'm losing my mind a bit. I have an observable of which I want to handle exceptions by using Catch(). I want to be able to access the item T that is moving through the observable chain within that Catch() and I thought this would be possible with a higher order observable that is Concat()ed afterwards, like so:
IObservable<RunData> obs = ...;
var safeObs = obs.Select(rd =>
.Select(rd => {
// simple toy example, could throw exception here in practice
// throw new Exception();
return (result: true, runData: rd);
})
.Catch((Exception e) => // try to catch any exception occurring within the stream, return a new tuple with result: false if that happens
{
return (Observable.Return((result: false, runData: rd))); // possible to access rd here
})
).Concat();
So far, so good.
But while testing this pattern I noticed that it breaks the assumption that I'm able to see all RunData instances when I subscribe to that safeObs. I've written the following test to showcase this:
[Test]
[Explicit]
public async Task TestHigherOrderExceptionHandling()
{
var counter = new Counter();
var useHigherOrderExceptionHandling = true; // test succeeds when false, fails when true
var obs = Observable.Create<RunData>(async (o) =>
{
await Task.Delay(100); // just here to justify the async nature
o.OnNext(new RunData(counter)); // produce a new RunData object, must be disposed later!
o.OnCompleted();
return Disposable.Empty;
})
.Concat(Observable.Empty<RunData>().Delay(TimeSpan.FromSeconds(1)))
.Repeat() // Resubscribe indefinitely after source completes
.Publish().RefCount() // see http://northhorizon.net/2011/sharing-in-rx/
;
// transforms the stream, exceptions might be thrown inside of stream, would like to catch them and handle them appropriately
IObservable<(bool result, RunData runData)> TransformRunDataToResult(IObservable<RunData> obs)
{
return obs.Select(rd => {
// simple toy example, could throw exception here in practice
// throw new Exception();
return (result: true, runData: rd);
});
}
IObservable<(bool result, RunData runData)> safeObs;
if (useHigherOrderExceptionHandling)
{
safeObs = obs.Select(rd =>
TransformRunDataToResult(obs)
.Catch((Exception e) => // try to catch any exception occurring within the stream, return a new tuple with result: false if that happens
{
return (Observable.Return((result: false, runData: rd)));
})
).Concat();
}
else
{
safeObs = TransformRunDataToResult(obs);
}
safeObs.Subscribe(
async (t) =>
{
var (result, runData) = t;
try
{
await Task.Delay(100); // just here to justify the async nature
Console.WriteLine($"Result: {result}");
}
finally
{
t.runData.Dispose(); // dispose RunData instance that was created by the observable above
}
});
await Task.Delay(5000); // give observable enough time to produce a few items
Assert.AreEqual(0, counter.Value);
}
// simple timer class, just here so we have a reference typed counter that we can pass around
public class Counter
{
public int Value { get; set; }
}
// data that is moving through observable pipeline, must be disposed at the end
public class RunData : IDisposable
{
private readonly Counter counter;
public RunData(Counter counter)
{
this.counter = counter;
Console.WriteLine("Created");
counter.Value++;
}
public void Dispose()
{
Console.WriteLine("Dispose called");
counter.Value--;
}
}
Running this test fails. There is one more instance of RunData created than there is disposed... why? Changing useHigherOrderExceptionHandling to false makes the test succeed.
EDIT:
I simplified the code (removed async code, limited repeats to make it predictable) and tried the suggestion, but I'm getting the same bad result... the test fails:
[Test]
[Explicit]
public async Task TestHigherOrderExceptionHandling2()
{
var counter = new Counter();
var useHigherOrderExceptionHandling = true; // test succeeds when false, fails when true
var obs = Observable.Create<RunData>(o =>
{
o.OnNext(new RunData(counter)); // produce a new RunData object, must be disposed later!
o.OnCompleted();
return Disposable.Empty;
})
.Concat(Observable.Empty<RunData>().Delay(TimeSpan.FromSeconds(1)))
.Repeat(3) // Resubscribe two more times after source completes
.Publish().RefCount() // see http://northhorizon.net/2011/sharing-in-rx/
;
// transforms the stream, exceptions might be thrown inside of stream, I would like to catch them and handle them appropriately
IObservable<(bool result, RunData runData)> TransformRunDataToResult(IObservable<RunData> obs)
{
return obs.Select(rd =>
{
// simple toy example, could throw exception here in practice
// throw new Exception();
return (result: true, runData: rd);
});
}
IObservable<(bool result, RunData runData)> safeObs;
if (useHigherOrderExceptionHandling)
{
safeObs = obs.Publish(_obs => _obs
.Select(rd => TransformRunDataToResult(_obs)
.Catch((Exception e) => Observable.Return((result: false, runData: rd)))
))
.Concat();
}
else
{
safeObs = TransformRunDataToResult(obs);
}
safeObs.Subscribe(
t =>
{
var (result, runData) = t;
try
{
Console.WriteLine($"Result: {result}");
}
finally
{
t.runData.Dispose(); // dispose RunData instance that was created by the observable above
}
});
await Task.Delay(4000); // give observable enough time to produce a few items
Assert.AreEqual(0, counter.Value);
}
Output:
Created
Created
Result: True
Dispose called
Created
Result: True
Dispose called
There's still a second subscription happening at the beginning(?) and there's one more RunData object created than is disposed.
It's not clear here what you're trying to accomplish.
First, your code mixes Tasks with Observables, which is generally something to avoid. You generally want to pick one or the other.
Second, I found that both versions of safeObs would fail tests, as I would expect: You're consistently incrementing, then consistently decrementing, but with (effectively) inconsistent time gaps in between the increments and decrements. Run this enough times and you'll be wrong eventually.
You also have a multiple subscription bug. If you collapse all your code into one fluid chain, this bug should stand out:
// this is roughly equivalent to var obs in your code
var obs2 = Observable.Interval(TimeSpan.FromSeconds(2))
.Select(_ => new RunData(counter))
.Publish()
.RefCount();
// this is equivalent to the higher order version of safeObs in your code
var safeObs2HigherOrder = obs2
.Select(rd => obs2
.Select(innerRd => (result: true, runData: innerRd))
.Catch((Exception e) => Observable.Return((result: false, runData: rd)))
))
.Concat();
Notice how safeObs2HigherOrder references obs2 twice, effectively subscribing twice. You can fix that as follows:
var safeObs2HigherOrder = obs.Publish(_obs => _obs
.Select(rd => _obs
.Select(innerRd => (result: true, runData: innerRd))
.Catch((Exception e) => Observable.Return((result: false, runData: rd)))
))
.Concat();
Lastly, the Concat at the end of the safeObs2HigherOrder should probably be a Switch or a Merge. Hard to tell when the larger problem aren't readily apparent.
I don't know if you were looking for a code review or an answer to a particular question, but your code needs quite a bit of work.

Disposal when chaining Observables from async

Assume I have the following Observable. (Note that the parsing logic lives in a different layer, and should be testable, so it must remain a separate method. Note also that the real loop is parsing XML and has various branching and exception handling).
IObservable<string> GetLinesAsync(StreamReader r)
{
return Observable.Create<string>(subscribeAsync: async (observer, ct) =>
{
//essentially force a continuation/callback to illustrate my problem
await Task.Delay(5);
while (!ct.IsCancellationRequested)
{
string readLine = await r.ReadLineAsync();
if (readLine == null)
break;
observer.OnNext(readLine);
}
});
}
I would like to use this, for example with another Observable that produces the StreamReader, as in the below, but in any case I cannot get the disposal to work.
[TestMethod]
public async Task ReactiveTest()
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "win.ini");
var source1 = Observable.Using(
() => File.OpenRead(filePath),
readFile => Observable.Using(() => new StreamReader(readFile),
reader => Observable.Return(reader)
)
);
//"GetLinesAsync" already exists. How can I use it?
var combined = source1
.SelectMany(GetLinesAsync);
int count = await combined.Count();
}
If you run this a few times (e.g. with breakpoints, etc), you should see that it blows up because the TextReader is closed. (In my actual problem it happens sporadically on ReadLineAsync but the Task.Delay makes it happen much more easily). Apparently the asynchronous nature causes the first observable to dispose the stream, and only after that does the continuation occur, and of course at that point the stream is already closed.
So:
is the first disposable with the usings set up right? I tried it other ways (see below*)
Is that the right way to do an async Observable (i.e. GetLinesAsync)? Is there anything else I need to do for that?
Is this a proper way to chain the observables together? Assume the GetLinesAsync already exists and if possible its signature shouldn't be changed (e.g. to take in IObservable<StreamReader>)
if this is the right way to glue together the observables, is there any way to get it working with async usage?
*this was another way I set up the first observerable
var source3 = Observable.Create<StreamReader>(observer =>
{
FileStream readFile = File.OpenRead(filePath);
StreamReader reader = new StreamReader(readFile);
observer.OnNext(reader);
observer.OnCompleted();
return new CompositeDisposable(readFile, reader);
});
You really need to make good use of the Defer and Using operators here.
Using is specifically for the case where you have a disposable resource that you would like to have created and finally disposed of when the subscription starts and completes respectively.
Defer is a way to ensure that you always create a new pipeline whenever you have a new subscription (read more on MSDN)
Your second approach is the way to go. You got this 100% right:
Observable.Using(
() => File.OpenRead(filePath),
readFile =>
Observable.Using(
() => new StreamReader(readFile),
reader =>
This will open and dispose of the resources at the correct time for each.
It's what goes before this block of code and what's after the reader => that you need to fix.
After the reader => is this:
Observable
.Defer(() => Observable.FromAsync(() => reader.ReadLineAsync()))
.Repeat()
.TakeWhile(x => x != null)));
That's the idiomatic way for Rx to read from a stream until completion.
The "before" block is just another Defer to ensure that you compute Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "win.ini") with each new subscriber. It's not necessary in this case because we know that the filePath won't change, but it's good practice and quite probably crucial when this value can change.
Here's the full code:
public async Task ReactiveTest()
{
IObservable<string> combined =
Observable.Defer(() =>
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "win.ini");
return
Observable.Using(
() => File.OpenRead(filePath),
readFile =>
Observable.Using(
() => new StreamReader(readFile),
reader =>
Observable
.Defer(() => Observable.FromAsync(() => reader.ReadLineAsync()))
.Repeat()
.TakeWhile(x => x != null)));
});
int count = await combined.Count();
}
I've tested it and it works superbly.
Given that you have a fixed signature for GetLines you can do this:
public IObservable<string> GetLines(StreamReader reader)
{
return Observable
.Defer(() => Observable.FromAsync(() => reader.ReadLineAsync()))
.Repeat()
.TakeWhile(x => x != null);
}
public async Task ReactiveTest()
{
IObservable<string> combined =
Observable.Defer(() =>
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "win.ini");
return
Observable.Using(
() => File.OpenRead(filePath),
readFile =>
Observable.Using(
() => new StreamReader(readFile),
GetLines));
});
int count = await combined.Count();
}
It also works and was tested.
The problem you are having is that your sequences return a single item, the reader. Making use of the reader, requires the file stream to be open. The file stream is unfortunately closed immediately after the stream reader is created:
StreamReader reader is created
OnNext(reader) is called
using block exits, disposing of stream
OnComplete is called, terminating the subscription
Oops!
To fix this, you must tie the lifetime of the StreamReader to the lifetime of the consumer rather than the producer. The original fault occurs because Observable.Using disposes the resource as soon as OnCompleted is called upon the source.
// Do not dispose of the reader when it is created
var readerSequence = Observable.Return(new StreamReader(ms));
var combined = readerSequence
.Select(reader =>
{
return Observable.Using(() => reader, resource => GetLines(resource));
})
.Concat();
I'm not a massive fan of this as you now rely on your consumer cleaning up the each StreamReader but I'm yet to formulate a better way!
So far this is the only thing that has worked while allowing me to keep using GetLinesAsync:
//"GetLinesAsync" already exists. How can I use it?
[TestMethod]
public async Task ReactiveTest2()
{
var combined2 = Observable.Create<string>(async observer =>
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "win.ini");
using (FileStream readFile = File.OpenRead(filePath))
{
using (StreamReader reader = new StreamReader(readFile))
{
await GetLinesAsync(reader)
.ForEachAsync(result => observer.OnNext(result));
}
}
});
int count = await combined2.Count();
}
This does not work reliably:
[TestMethod]
public async Task ReactiveTest3()
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "win.ini");
var source1 = Observable.Defer(() => Observable.Using(
() => File.OpenRead(filePath),
readFile => Observable.Using(() => new StreamReader(readFile),
reader => Observable.Return(reader)
)
));
//"GetLines" already exists. How can I use it?
var combined = source1
.SelectMany(reader => Observable.Defer(() => GetLinesAsync(reader)));
int count = await combined.Count();
}
It only seems to work if there's one Observable, as per Enigmativity's solution:
[TestMethod]
public async Task ReactiveTest4()
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "win.ini");
var source1 = Observable.Using(
() => File.OpenRead(filePath),
readFile => Observable.Using(() => new StreamReader(readFile),
reader => GetLinesAsync(reader)
)
);
int count = await source1.Count();
}
But I haven't found a way to preserve the separation between the two Observables (which I do for layering and unit test purposes) and have it work right, so I don't consider the question answered.

Lazy observable sequence that replays value or error

I am trying to create an observable pipeline with the following characteristics:
is lazy (does nothing until somebody subscribes)
executes at most once regardless of how many subscriptions are received
replays its resulting value, if any OR
replays its resulting error, if any
For the life of me, I can't figure out the correct semantics to accomplish this. I thought it would be a simple case of doing something like this:
Observable
.Defer(() => Observable
.Start(() => { /* do something */ })
.PublishLast()
.ConnectUntilCompleted());
Where ConnectUntilCompleted just does what it sounds like:
public static IObservable<T> ConnectUntilCompleted<T>(this IConnectableObservable<T> #this)
{
#this.Connect();
return #this;
}
This seems to work when the observable terminates successfully, but not when there's an error. Any subscribers do not receive the error:
[Fact]
public void test()
{
var o = Observable
.Defer(() => Observable
.Start(() => { throw new InvalidOperationException(); })
.PublishLast()
.ConnectUntilCompleted());
// this does not throw!
o.Subscribe();
}
Can anyone tell me what I'm doing wrong? Why doesn't Publish replay any error it receives?
UPDATE: it gets even stranger:
[Fact]
public void test()
{
var o = Observable
.Defer(() => Observable
.Start(() => { throw new InvalidOperationException(); })
.PublishLast()
.ConnectUntilCompleted())
.Do(
_ => { },
ex => { /* this executes */ });
// this does not throw!
o.Subscribe();
o.Subscribe(
_ => { },
ex => { /* even though this executes */ });
}
Try this version of you ConnectUntilCompleted method:
public static IObservable<T> ConnectUntilCompleted<T>(this IConnectableObservable<T> #this)
{
return Observable.Create<T>(o =>
{
var subscription = #this.Subscribe(o);
var connection = #this.Connect();
return new CompositeDisposable(subscription, connection);
});
}
The allows Rx to behave properly.
Now I've added to it to help show what's going on:
public static IObservable<T> ConnectUntilCompleted<T>(this IConnectableObservable<T> #this)
{
return Observable.Create<T>(o =>
{
var disposed = Disposable.Create(() => Console.WriteLine("Disposed!"));
var subscription = Observable
.Defer<T>(() => { Console.WriteLine("Subscribing!"); return #this; })
.Subscribe(o);
Console.WriteLine("Connecting!");
var connection = #this.Connect();
return new CompositeDisposable(disposed, subscription, connection);
});
}
Now your observable looks like this:
var o =
Observable
.Defer(() =>
Observable
.Start(() =>
{
Console.WriteLine("Started.");
throw new InvalidOperationException();
}))
.PublishLast()
.ConnectUntilCompleted();
The final key thing is to actually handle the errors in the subscription - so it's not enough to simply do o.Subscribe().
So do this:
o.Subscribe(
x => Console.WriteLine(x),
e => Console.WriteLine(e.Message),
() => Console.WriteLine("Done."));
o.Subscribe(
x => Console.WriteLine(x),
e => Console.WriteLine(e.Message),
() => Console.WriteLine("Done."));
o.Subscribe(
x => Console.WriteLine(x),
e => Console.WriteLine(e.Message),
() => Console.WriteLine("Done."));
When I run that I get this:
Subscribing!
Connecting!
Subscribing!
Connecting!
Subscribing!
Connecting!
Started.
Operation is not valid due to the current state of the object.
Disposed!
Operation is not valid due to the current state of the object.
Disposed!
Operation is not valid due to the current state of the object.
Disposed!
Note that "Started" only appears once, but the error is reported three times.
(Sometimes Started appears higher up in the list after the first subscription.)
I think this is what you wanted from your description.
Just to support #Engimativity's answer, i want to show how you should be runing your tests so you stop getting these "surprises". Your tests are non-deterministic because they are multi-threaded/concurrent. Your use of Observable.Start without providing an IScheduler is problematic. If you run your tests with a TestScheduler your tests will now be singlethreaded and determinisitic
[Test]
public void Test()
{
var testScheduler = new TestScheduler();
var o = Observable
.Defer(() => Observable
.Start(() => { throw new InvalidOperationException(); }, testScheduler)
.PublishLast()
.ConnectUntilCompleted());
var observer = testScheduler.CreateObserver<Unit>();
o.Subscribe(observer);
testScheduler.Start();
CollectionAssert.IsNotEmpty(observer.Messages);
Assert.AreEqual(NotificationKind.OnError, observer.Messages[0].Value.Kind);
}
An alternative way to achieve your requirements could be:
var lazy = new Lazy<Task>(async () => { /* execute once */ }, isThreadSafe: true);
var o = Observable.FromAsync(() => lazy.Value);
When subscribed for the first time, lazy would create (and execute) the task. For other subscriptions, lazy would return the same (possibly already completed or failed) task.

Rx: How to perform an action on inner observable finish with minimum boilerplate?

In an effort to lean Rx I'm writing a simple application which imitates reading a file and supports cancellation.
It works like this:
When the user presses the "Run" button the program
shows the busy indicator,
reads a file in chunks, which is a long process, reporting the length of each one,
hides the busy indicator when the reading process is finished (or canceled, see below).
If the user presses the "Cancel" button the file reading process gets canceled.
If the user presses the "Run" button while a previous operation is still in progress, the previous operation gets canceled and a new one starts.
I'm looking for a way to implement this with minimum glue code while still maintaining the look of a simple sequence of synchronous operations in which the natural control flow is easily seen.
The main problem seems to be hiding the busy indicator when the inner observable (the reader) finishes, the outer observable being the "Run" click event sequence.
So far I came up with these two versions of the main piece of the program:
subscriber-based:
IObservable<byte[]> xs =
runClick
.Do((Action<EventPattern<EventArgs>>)(_ => ShowBusy(true)))
.Do(_ => ShowError(false))
.Select(_ =>
reader
.TakeUntil(cancelClick)
.Publish(ob =>
{
ob.Subscribe(
b => { },
ex =>
{
Console.WriteLine("ERROR: " + ex);
ShowBusy(false);
ShowError(true);
},
() => ShowBusy(false));
return ob;
}))
.Switch();
xs = xs.Catch<byte[], Exception>(e => xs);
and concat-based:
IObservable<byte[]> xs =
runClick
.Do((Action<EventPattern<EventArgs>>)(_ => ShowBusy(true)))
.Do(_ => ShowError(false))
.Select(_ =>
reader
.TakeUntil(cancelClick)
.DoAfter(() =>
ShowBusy(false)))
.Switch();
xs = xs.Catch<byte[], Exception>(e =>
{
Console.WriteLine("ERROR: " + e);
ShowBusy(false);
ShowError(true);
return xs;
});
with one custom method DoAfter
public static IObservable<T> DoAfter<T>(this IObservable<T> observable, Action action)
{
IObservable<T> appended = observable.Concat(
Observable.Create<T>(o =>
{
try
{
action();
}
catch (Exception ex)
{
o.OnError(ex);
return Disposable.Empty;
}
o.OnCompleted();
return Disposable.Empty;
}));
return appended;
}
(see the whole application code here).
Neither implementation satisfies me completely: the first is rather verbose, the second forces me to write my own method for a rather simple task which I would expect to be covered by standard means. Am I missing something?
It turned out what I was missing was an overload of the Do method. I can use it instead of my custom DoAfter method and then the code looks good.
IObservable<byte[]> xs =
runClick
.Do((Action<EventPattern<EventArgs>>)(_ => ShowBusy(true)))
.Do(_ => ShowError(false))
.Select(_ =>
reader
.TakeUntil(cancelClick)
.Do(
b => { },
ex =>
{
Console.WriteLine("ERROR: " + ex);
ShowBusy(false);
ShowError(true);
},
() => ShowBusy(false)
))
.Switch();
xs = xs.Catch<byte[], Exception>(e => xs);

How to re-subscribe to sequence in particular point?

I'm trying to solve the following:
a) subscriber receives events from IObservable for some time. Then it unsubscribes, do some stuff and then subscribe again. Here it should start receiving events from exactly the same point where unsubscription was performed.
b) Such behavior is desirable for multiple subscribers model. E.g. when one has unsubscribed, others should continue receiving events.
Are there any suggestions from the RX side?
Thanks in advance!
Here's a reasonably simple Rx way to do what you want copied from my answer to this other question. I've created an extension method called Pausable that takes a source observable and a second observable of boolean that pauses or resumes the observable.
public static IObservable<T> Pausable<T>(
this IObservable<T> source,
IObservable<bool> pauser)
{
return Observable.Create<T>(o =>
{
var paused = new SerialDisposable();
var subscription = Observable.Publish(source, ps =>
{
var values = new ReplaySubject<T>();
Func<bool, IObservable<T>> switcher = b =>
{
if (b)
{
values.Dispose();
values = new ReplaySubject<T>();
paused.Disposable = ps.Subscribe(values);
return Observable.Empty<T>();
}
else
{
return values.Concat(ps);
}
};
return pauser.StartWith(false).DistinctUntilChanged()
.Select(p => switcher(p))
.Switch();
}).Subscribe(o);
return new CompositeDisposable(subscription, paused);
});
}
It can be used like this:
var xs = Observable.Generate(
0,
x => x < 100,
x => x + 1,
x => x,
x => TimeSpan.FromSeconds(0.1));
var bs = new Subject<bool>();
var pxs = xs.Pausable(bs);
pxs.Subscribe(x => { /* Do stuff */ });
Thread.Sleep(500);
bs.OnNext(true);
Thread.Sleep(5000);
bs.OnNext(false);
Thread.Sleep(500);
bs.OnNext(true);
Thread.Sleep(5000);
bs.OnNext(false);
It sounds like you need a "pausable" stream. Assuming that only 1 subscriber will handle the values at a time (while the other subscribers just wait), this solution is probably what you need.

Categories