This is a weird 'problem', I'm not sure what the best way to handle it is.
To simplify, let's say that I've got an observable source with some data reading coming from 'outside':
{ Value, TimeStamp }
I'm putting that through Observable.Scan so that I can output:
{ Value, TimeStamp, TimeDelta }
This means that my data always comes out 'one late', but that's not a problem.
We're 'recording' from this observable, and when you stop one recording, there's still one data value 'stuck' waiting for it's follower.
Even that's not a problem. The problem is that when you go to start recording again, the last value from the previous 'recording' gets stuck on to the beginning of the new one.
The most obvious thing to do is just to unsubscribe and resubscribe, but.... it's not that simple, because this scanned source is not only recorded, but also sent to the UI, and used for further calculations down the line: so I'd have to do an enormous unsubscribe/resubscribe.
I'm trying to think of a way to inject some kind of 'reset' data, but not sure how one goes about sending information back 'up' the observable stream...
Maybe I've just bitten off more than I can chew? Or used too much Observable?
There are going to be a number of ways to do this, but one that is fairly easy is to use the .Switch() operator.
It essentially works like this: if you have an IObservable<IObservable<T>> you can then call .Switch() to turn it into an IObservable<T> where it basically subscribes to the last value produced by the outer observable and unsubscribes to the previously produced observable.
Now that sounds a bit funky, but here's how it can work. Given you have an observable called outsideObservable then you defining a second observable (resubscribeObservable) that produces a value every time you want to resubscribe, and you subscribe to them like this:
var subscription =
resubscribeObservable
.Select(_ => outsideObservable)
.Switch()
.Subscribe(x =>
{
/* Do stuff here */
});
Now to resubscribe to outsideObservable you just have to produce a value from resubscribeObservable.
The easiest way to do this is to define it like var resubscribeObservable = new Subject<Unit>(); and then call resubscribeObservable.OnNext(Unit.Default); every time you want to resubscribe.
Alternatively if you have some event, say a user clicking a button, then you could use an observable based on that event as your resubscribeObservable.
Integrating suggestions from the comments, this would look something like:
var factory = Observable.Defer(() => outsideObservable);
var resetterObservable = new Subject<Unit>();
var resettableObservable =
resetterObservable
.StartWith(Unit.Default)
.Select(_ => factory)
.Switch()
.Publish()
.RefCount();
The Publish().RefCount() is just to protect the outsideObservable from multiple simultaneous subscriptions.
This is what I've boiled the accepted answer down to. Not yet in production, but tests seem to show it does what I want.
public interface IResetter
{
IObservable<T> MakeResettable<T>(Func<IObservable<T>> selector);
}
public class Resetter : IResetter
{
private Subject<Unit> _Resetter = new Subject<Unit>();
public void Reset()
{
_Resetter.OnNext(Unit.Default);
}
public IObservable<T> MakeResettable<T>(Func<IObservable<T>> selector)
{
return
_Resetter
.StartWith(Unit.Default)
.Select(_ => Observable.Defer(selector))
.Switch()
.Publish().RefCount();
}
}
Related
I am having the following problem: with RethinkDB using RunChangesAsync method runs once and when used, it starts listening to changes on a given query. When the query changes, you are given the Cursor<Change<Class>> , which is a delta between the initial state and the actual state.
My question is how can I make this run continuously?
If I use:
while(true)
{
code.... //changes happening while program is here
....../
...RunChangesAsync();
/......processed buffered items
code //new changes here
}
If there are changes happening where i pointed in the code, they would not be caught by the RunChanges. The only changes that would be caught would be while RunChanges is listening. Not before ..or after it retrieves the results.
So I tried wrapping the RunChanges in an observable but it does not listen continuously for changes as I would have expected...it just retrieves 2 null items (garbage I suppose) and ends.
Observable
public IObservable<Cursor<Change<UserStatus?>>> GetObservable() =>
r.Db(Constants.DB_NAME).Table(Constants.CLIENT_TABLE).RunChangesAsync<UserStatus?>(this.con,CancellationToken.None).ToObservable();
Observer
class PlayerSubscriber : IObserver<Cursor<Change<UserStatus?>>>
{
public void OnCompleted() => Console.WriteLine("Finished");
public void OnError(Exception error) => Console.WriteLine("error");
public void OnNext(Cursor<Change<UserStatus?>> value)
{
foreach (var item in value.BufferedItems)
Console.WriteLine(item);
}
}
Program
class Program
{
public static RethinkDB r = RethinkDB.R;
public static bool End = false;
static async Task Main(string[] args)
{
var address = new Address { Host = "127.0.0.1", Port = 28015 };
var con = await r.Connection().Hostname(address.Host).Port(address.Port).ConnectAsync();
var database = new Database(r, con);
var obs = database.GetObservable();
var sub = new PlayerSubscriber();
var disp = obs.Subscribe(sub);
Console.ReadKey();
Console.WriteLine("Hello World!");
}
}
When I am debugging as you can see, the OnNext method of the Observer is executed only once (returns two null objects) and then it closes.
P.S: Database is just a wrapper around rethinkdb queries. The only method used is GetObservable which I posted it. The UserStatus is a POCO.
When creating a change feed, you'll want to create one change feed object. For example, when you get back a Cursor<Change<T>> after running .RunChangesAsync(); that is really all you need.
The cursor object you get back from query.RunChangesAsync() is your change feed object that you will use for the entire lifetime you want to receive changes.
In your example:
while(true)
{
code.... //changes happening while program is here
....../
...RunChangesAsync();
/......processed buffered items
code //new changes here
}
Having .RunChangesAsync(); in a while loop is not the correct approach. You don't need to re-run the query again and get another Cursor<Change<T>>. I'll explain how this works at the end of this post.
Also, do not use cursor.BufferedItems on the cursor object. The cursor.BufferedItems property on the cursor is not meant to consumed by your code directly; the cursor.BufferedItems property is only exposed for those special situations where you want to "peek ahead" inside the cursor object (client-side) for items that are ready to be consumed that are specific to your change feed query.
The proper way to consume items in your change feed is to enumerate over the cursor object itself as shown below:
var cursor = await query.RunChangesAsync(conn);
foreach (var item in cursor){
Console.WriteLine(item);
}
When the cursor runs out of items, it will make a request to the RethinkDB server for more items. Keep in mind, each iteration of the foreach loop can be potentially a blocking call. For example, the foreach loop can block indefinitely when 1) there are no items on the client-side to be consumed (.BufferedItems.Count == 0) and 2) there are no items that have been changed on the server-side according to your change feed query criteria. under these circumstances, the foreach loop will block until RethinkDB server sends you an item that is ready to be consumed.
Documentation about using Reactive Extensions and RethinkDB in C#
There is a driver unit test that shows how .NET Reactive Extensions can work here.
Specifically, Lines 31 - 47 in this unit test set up a change feed with Reactive Extensions:
var changes = R.Db(DbName).Table(TableName)
//.changes()[new {include_states = true, include_initial = true}]
.Changes()
.RunChanges<JObject>(conn);
changes.IsFeed.Should().BeTrue();
var observable = changes.ToObservable();
//use a new thread if you want to continue,
//otherwise, subscription will block.
observable.SubscribeOn(NewThreadScheduler.Default)
.Subscribe(
x => OnNext(x),
e => OnError(e),
() => OnCompleted()
);
Additionally, here is a good example and explanation of what happens and how to consume a change feed with C#:
Hope that helps.
Thanks,
Brian
If you have an operation that has the signature Task<int> ReadAsync(), then the way to set up polling, is like this:
IObservable<int> PollRead(TimeSpan interval)
{
return
Observable
.Interval(interval)
.SelectMany(n => Observable.FromAsync(() => ReadAsync()));
}
I'd also caution about you creating your own implementation of IObservable<T> - it's fraught with danger. You should use Observer.Create(...) if you are creating your own observer that you want to hand around. Generally you don't even do that.
Wondering if anyone can think of an elegant solution to this use case:
I am consuming an observable (IObservable of type TEntity) which is providing me a stream of entities. If any of those entities are updated, then the provider of the observable will push down the updated entity.
I am using a Replay() on this stream so that I only need to subscribe to the underlying stream once and so that late subscribers can see all the values. The problem is that there is potential for a memory-leak here, because the Replay() will hold onto all the updates it sees, whereas all I need is the latest update for each entity.
I could replace the Replay() with a Scan() which allows me to maintain the latest updates only, but then I would have to push out a Dictionary of all the updates observed so far, rather than just the specific entity that has changed.
The only solution I can think of is to use a Scan() as above, but in the Scan() implementation I will push all updates into an Subject. Subscribers to the IObservable I will expose will receive a merge of the Snapshot stored in the Scan() dictionary plus any updates, as follows:
private Subject<Entity> _updateSubject = new Subject<Entity>();
private IObservable<Dictionary<string, Entity>> _subscriptionStream;
//called once on initialisation
private void CreateSubscription()
{
IObservable<Entity> source = GetSomeLongRunningSubscriptionStream();
_subscriptionStream = source
.Scan(new Dictionary<string, Entity>(), (accumulator,update) =>
{
accumulator[update.ID] = update;
_updateSubject.OnNext(update);
return accumulator;
})
.Replay(1);
}
//called each time a consumer wants access to the stream
public IObservable<Entity> GetStream()
{
return _subscriptionStream.Take(1).SelectMany(x => x).Select(x => x.Value)
.Merge(_updateSubject.AsObservable());
}
Can anyone think of a more elegant solution with holds the state within a single stream rather than resorting to Subjects?
Thanks
************** Edit **************
As per my comment, I've gone with something similar to this. Let me know your thoughts
//called once on initialisation
private void CreateSubscription()
{
_baseSubscriptionObservable = GetSomeLongRunningSubscriptionStream ().Publish();
_snapshotObservable = _baseSubscriptionObservable
.Scan(new Dictionary<string,Entity>(), (accumulator, update) =>
{
accumulator[update.ID] = update;
return accumulator;
})
.StartWith(new Dictionary<string, Entity>())
.Replay(1);
_baseSubscriptionObservable.Connect ();
_snapshotObservable.Connect ();
}
public IObservable<Entity> GetStream()
{
return _snapshotObservable.Take (1).Select (x => x.Values).SelectMany (x => x)
.Merge (_baseSubscriptionObservable);
}
I generally like what you're doing, but there are a number of issues that I can see.
To start with you've split CreateSubscription and GetStream into two methods, with the idea that you'll have one underlying subscription to the GetSomeLongRunningSubscriptionStream() stream. Unfortunately, in this case, you'll have zero subscriptions regardless how many subscriptions you get to your final observable as .Replay(1) returns an IConnectableObservable<> which you need to call .Connect() on to begin the flow of values.
The next thing is that you're updating your accumulator with the latest value and then in GetStream you're adding in the latest value along with merging in a flattened stream of your accumulator. You're returning the latest value twice each time.
Here's how I would suggest that you do it:
private IObservable<IList<Timestamped<Entity>>> GetStream()
{
return
Observable
.Create<IList<Timestamped<Entity>>>(o =>
GetSomeLongRunningSubscriptionStream()
.Timestamp()
.Scan(
new Dictionary<string, Timestamped<Entity>>(),
(accumulator, update) =>
{
accumulator[update.Value.ID] = update;
return accumulator;
})
.Select(x => x.Select(y => y.Value).ToList())
.Replay(1)
.RefCount()
.Subscribe(o));
}
It's almost always best to avoid any state when using Rx (that isn't localized within the observable). So I've merged together CreateSubscription and GetStream into a single GetStream method and I've encapsulated the whole observable into a Observable.Create.
In order to avoid pushing out values twice and to facilitate your ability to know what the latest update is I've added a call to .Timestamp() to put the latest time an Entity was returned.
I've kept the .Scan(...) with the dictionary, but it is now a Dictionary<string, Timestamped<Entity>>.
For each value added/updated I then flatten the dictionary and return the underlying values as a list. At this point you could order the list to make sure that the latest values are either first or last to suit your needs.
I've then used the .Replay(1).RefCount() combination to turn the IConnectableObservable<> returned by .Replay(1) back into an IObservable<>, with the understanding that you'll dispose of the underlying subscription when all subscribers dispose. This is probably the most crucial part of your query. It should be done this way. This is the Rx way of ensuring that you avoid memory leaks.
If you desperately need to keep the underlying connection open then you would need to encapsulate all of your code within a class that implements IDisposable to clean up the .Connect() that you would require.
Something like this:
public class EntityStream : IDisposable
{
private IDisposable _connection = null;
public EntityStream(IObservable<Entity> someLongRunningSubscriptionStream)
{
_stream =
someLongRunningSubscriptionStream
.Timestamp()
.Scan(
new Dictionary<string, Timestamped<Entity>>(),
(accumulator, update) =>
{
accumulator[update.Value.ID] = update;
return accumulator;
})
.Select(x => x.Select(y => y.Value).ToList())
.Replay(1);
_connection = _stream.Connect();
}
private IConnectableObservable<IList<Timestamped<Entity>>> _stream = null;
public IObservable<IList<Timestamped<Entity>>> GetStream()
{
return _stream.AsObservable();
}
public void Dispose()
{
if (_connection != null)
{
_connection.Dispose();
_connection = null;
}
}
}
I so very rarely do this though. I would thoroughly recommend the doing the first method. You should only mix OOP and Rx when you have to.
Please let me know if you need any clarification.
FWIW - I'm scrapping the previous version of this question in favor of different one along the same way after asking for advice on meta
I have a webservice that contains configuration data. I would like to call it at regular intervals Tok in order to refresh the configuration data in the application that uses it. If the service is in error (timeout, down, etc) I want to keep the data from the previous call and call the service again after a different time interval Tnotok. Finally I want the behavior to be testable.
Since managing time sequences and testability seems like a strong point of the Reactive Extensions, I started using an Observable that will be fed by a generated sequence. Here is how I create the sequence:
Observable.Generate<DataProviderResult, DataProviderResult>(
// we start with some empty data
new DataProviderResult() {
Failures = 0
, Informations = new List<Information>()},
// never stop
(r) => true,
// there is no iteration
(r) => r,
// we get the next value from a call to the webservice
(r) => FetchNextResults(r),
// we select time for next msg depending on the current failures
(r) => r.Failures > 0 ? tnotok : tok,
// we pass a TestScheduler
scheduler)
.Suscribe(r => HandleResults(r));
I have two problems currently:
It looks like I am creating a hot observable. Even trying to use Publish/Connect I have the suscribed action missing the first event. How can I create it as a cold observable?
myObservable = myObservable.Publish();
myObservable.Suscribe(r => HandleResults(r));
myObservable.Connect() // doesn't call onNext for first element in sequence
When I suscribe, the order in which the suscription and the generation seems off, since for any frame the suscription method is fired before the FetchNextResults method. Is it normal? I would expect the sequence to call the method for frame f, not f+1.
Here is the code that I'm using for fetching and suscription:
private DataProviderResult FetchNextResults(DataProviderResult previousResult)
{
Console.WriteLine(string.Format("Fetching at {0:hh:mm:ss:fff}", scheduler.Now));
try
{
return new DataProviderResult() { Informations = dataProvider.GetInformation().ToList(), Failures = 0};
}
catch (Exception)
{}
previousResult.Failures++;
return previousResult;
}
private void HandleResults(DataProviderResult result)
{
Console.WriteLine(string.Format("Managing at {0:hh:mm:ss:fff}", scheduler.Now));
dataResult = result;
}
Here is what I'm seeing that prompted me articulating these questions:
Starting at 12:00:00:000
Fetching at 12:00:00:000 < no managing the result that has been fetched here
Managing at 12:00:01:000 < managing before fetching for frame f
Fetching at 12:00:01:000
Managing at 12:00:02:000
Fetching at 12:00:02:000
EDIT: Here is a bare bones copy-pastable program that illustrates the problem.
/*using System;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using Microsoft.Reactive.Testing;*/
private static int fetchData(int i, IScheduler scheduler)
{
writeTime("fetching " + (i+1).ToString(), scheduler);
return i+1;
}
private static void manageData(int i, IScheduler scheduler)
{
writeTime("managing " + i.ToString(), scheduler);
}
private static void writeTime(string msg, IScheduler scheduler)
{
Console.WriteLine(string.Format("{0:mm:ss:fff} {1}", scheduler.Now, msg));
}
private static void Main(string[] args)
{
var scheduler = new TestScheduler();
writeTime("start", scheduler);
var datas = Observable.Generate<int, int>(fetchData(0, scheduler),
(d) => true,
(d) => fetchData(d, scheduler),
(d) => d,
(d) => TimeSpan.FromMilliseconds(1000),
scheduler)
.Subscribe(i => manageData(i, scheduler));
scheduler.AdvanceBy(TimeSpan.FromMilliseconds(3000).Ticks);
}
This outputs the following:
00:00:000 start
00:00:000 fetching 1
00:01:000 managing 1
00:01:000 fetching 2
00:02:000 managing 2
00:02:000 fetching 3
I don't understand why the managing of the first element is not picked up immediately after its fetching. There is one second between the sequence effectively pulling the data and the data being handed to the observer. Am I missing something here or is it expected behavior? If so is there a way to have the observer react immediately to the new value?
You are misunderstanding the purpose of the timeSelector parameter. It is called each time a value is generated and it returns a time which indicates how long to delay before delivering that value to observers and then generating the next value.
Here's a non-Generate way to tackle your problem.
private DataProviderResult FetchNextResult()
{
// let exceptions throw
return dataProvider.GetInformation().ToList();
}
private IObservable<DataProviderResult> CreateObservable(IScheduler scheduler)
{
// an observable that produces a single result then completes
var fetch = Observable.Defer(
() => Observable.Return(FetchNextResult));
// concatenate this observable with one that will pause
// for "tok" time before completing.
// This observable will send the result
// then pause before completing.
var fetchThenPause = fetch.Concat(Observable
.Empty<DataProviderResult>()
.Delay(tok, scheduler));
// Now, if fetchThenPause fails, we want to consume/ignore the exception
// and then pause for tnotok time before completing with no results
var fetchPauseOnErrors = fetchThenPause.Catch(Observable
.Empty<DataProviderResult>()
.Delay(tnotok, scheduler));
// Now, whenever our observable completes (after its pause), start it again.
var fetchLoop = fetchPauseOnErrors.Repeat();
// Now use Publish(initialValue) so that we remember the most recent value
var fetchLoopWithMemory = fetchLoop.Publish(null);
// YMMV from here on. Lets use RefCount() to start the
// connection the first time someone subscribes
var fetchLoopAuto = fetchLoopWithMemory.RefCount();
// And lets filter out that first null that will arrive before
// we ever get the first result from the data provider
return fetchLoopAuto.Where(t => t != null);
}
public MyClass()
{
Information = CreateObservable();
}
public IObservable<DataProviderResult> Information { get; private set; }
Generate produces cold observable sequences, so that is my first alarm bell.
I tried to pull your code into linqpad* and run it and changed it a bit to focus on the problem. It seems to me that you have the Iterator and ResultSelector functions confused. These are back-to-front. When you iterate, you should take the value from your last iteration and use it to produce your next value. The result selector is used to pick off (Select) the value form the instance you are iterating on.
So in your case, the type you are iterating on is the type you want to produce values of. Therefore keep your ResultSelector function just the identity function x=>x, and your IteratorFunction should be the one that make the WebService call.
Observable.Generate<DataProviderResult, DataProviderResult>(
// we start with some empty data
new DataProviderResult() {
Failures = 0
, Informations = new List<Information>()},
// never stop
(r) => true,
// we get the next value(iterate) by making a call to the webservice
(r) => FetchNextResults(r),
// there is no projection
(r) => r,
// we select time for next msg depending on the current failures
(r) => r.Failures > 0 ? tnotok : tok,
// we pass a TestScheduler
scheduler)
.Suscribe(r => HandleResults(r));
As a side note, try to prefer immutable types instead of mutating values as you iterate.
*Please provide an autonomous working snippet of code so people can better answer your question. :-)
I've been trying to create an observable which streams a state-of-the-world (snapshot) from a repository cache, followed by live updates from a separate feed. The catch is that the snapshot call is blocking, so the updates have to be buffered during that time.
This is what I've come up with, a little simplified. The GetStream() method is the one I'm concerned with. I'm wondering whether there is a more elegant solution. Assume GetDataFeed() pulses updates to the cache all day long.
private static readonly IConnectableObservable<long> _updateStream;
public static Constructor()
{
_updateStream = GetDataFeed().Publish();
_updateStream.Connect();
}
static void Main(string[] args)
{
_updateStream.Subscribe(Console.WriteLine);
Console.ReadLine();
GetStream().Subscribe(l => Console.WriteLine("Stream: " + l));
Console.ReadLine();
}
public static IObservable<long> GetStream()
{
return Observable.Create<long>(observer =>
{
var bufferedStream = new ReplaySubject<long>();
_updateStream.Subscribe(bufferedStream);
var data = GetSnapshot();
// This returns the ticks from GetSnapshot
// followed by the buffered ticks from _updateStream
// followed by any subsequent ticks from _updateStream
data.ToObservable().Concat(bufferedStream).Subscribe(observer);
return Disposable.Empty;
});
}
private static IObservable<long> GetDataFeed()
{
var feed = Observable.Interval(TimeSpan.FromSeconds(1));
return Observable.Create<long>(observer =>
{
feed.Subscribe(observer);
return Disposable.Empty;
});
}
Popular opinion opposes Subjects as they are not 'functional', but I can't find a way of doing this without a ReplaySubject. The Replay filter on a hot observable wouldn't work because it would replay everything (potentially a whole day's worth of stale updates).
I'm also concerned about race conditions. Is there a way to guarantee sequencing of some sort, should an earlier update be buffered before the snapshot? Can the whole thing be done more safely and elegantly with other RX operators?
Thanks.
-Will
Whether you use a ReplaySubject or the Replay function really makes no difference. Replay uses a ReplaySubject under the hood. I'll also note that you are leaking subscriptions like mad, which can cause a resource leak. Also, you put no limit on the size of the replay buffer. If you watch the observable all day long, then that replay buffer will keep growing and growing. You should put a limit on it to prevent that.
Here is an updated version of GetStream. In this version I take the simplistic approach of just limitting the Replay to the most recent 1 minute of data. This assumes that GetData will always complete and the observer will observe the results within that 1 minute. Your mileage may vary and you can probably improve upon this scheme. But at least this way when you have watched the observable all day long, that buffer will not have grown unbounded and will still only contain a minute's worth of updates.
public static IObservable<long> GetStream()
{
return Observable.Create<long>(observer =>
{
var updateStreamSubscription = new SingleAssignmentDisposable();
var sequenceDisposable = new SingleAssignmentDisposable();
var subscriptions = new CompositeDisposable(updateStreamDisposable, sequenceDisposable);
// start buffering the updates
var bufferedStream = _updateStream.Replay(TimeSpan.FromMinutes(1));
updateStreamSubscription.Disposable = bufferedStream.Connect();
// now retrieve the initial snapshot data
var data = GetSnapshot();
// subscribe to the snapshot followed by the buffered data
sequenceDisposable.Disposable = data.ToObservable().Concat(bufferedStream).subscribe(observer);
// return the composite disposable which will unsubscribe when the observer wishes
return subscriptions;
});
}
As for your questions about race conditions and filtering out "old" updates...if your snapshot data includes some sort of version information, and your update stream also providers version information, then you can effectively measure the latest version returned by your snapshot query and then filter the buffered stream to ignore values for older versions. Here is a rough example:
public static IObservable<long> GetStream()
{
return Observable.Create<long>(observer =>
{
var updateStreamSubscription = new SingleAssignmentDisposable();
var sequenceDisposable = new SingleAssignmentDisposable();
var subscriptions = new CompositeDisposable(updateStreamDisposable, sequenceDisposable);
// start buffering the updates
var bufferedStream = _updateStream.Replay(TimeSpan.FromMinutes(1));
updateStreamSubscription.Disposable = bufferedStream.Connect();
// now retrieve the initial snapshot data
var data = GetSnapshot();
var snapshotVersion = data.Length > 0 ? data[data.Length - 1].Version : 0;
var filteredUpdates = bufferedStream.Where(update => update.Version > snapshotVersion);
// subscribe to the snapshot followed by the buffered data
sequenceDisposable.Disposable = data.ToObservable().Concat(filteredUpdates).subscribe(observer);
// return the composite disposable which will unsubscribe when the observer wishes
return subscriptions;
});
}
I have successfully used this pattern when merging live updates with a stored snapshot. I haven't yet found an elegant Rx operator that already does this without any race conditions. But the above method could probably be turned into such. :)
Edit: Note I have left out error handling in the examples above. In theory the call to GetSnapshot could fail and you'd leak the subscription to the update stream. I suggest wrapping everything after the CompositeDisposable declaration in a try/catch block, and in the catch handler, ensure call subscriptions.Dispose() before re-throwing the exception.
I have an IObservable<Packet> which is a hot observable, that allows different subscriptions to analyze incoming packets.
I want to write a method, that sends some data with and ID, and waits for response with the same ID. Pseudocode:
void SendData(byte[] data, int retries, int timeout, Action<Packet> success, Action fail)
{
var sequenceId = GetSequenceId();
_port.SendData(data, sequenceId);
_packetStream.Where(p => p.SequenceId == sequenceId)
.Take(1)
.WaitForTimeout(timeout)
.WaitForRetry(retries)
.Subscribe(success) //Need to unsubscribe after packet is received
//If we didn't receive an answer packet, then call fail() action
}
Don't really know, how this stuff is usually done with Reactive Extensions. Would be really glad to receive some suggestions. Thanks.
The code in your question looks close to right. The two "wait for" methods exist in the Rx framework (Timeout and Retry). I would recommend you change your method to return an IObservable and drop the success and fail parameter. Doing so "keeps you in the monad" and lets you chain further operators onto the observable if needed. The success and fail parameters are instead used when you subscribe to the resulting observable (as OnNext and OnError respectively).
I assume the data should be resent on a timeout (otherwise you are not really retrying). To do that, you can use Observable.Create to send the data upon subscription.
IObservable<Packet> SendData(byte[] data, int retries, TimeSpan timeout)
{
//only get the sequence id once per call to SendData, regardless of retries
var sequenceId = GetSequenceId();
return Observable.Create(obs =>
{ //this code runs every time you subscribe
_port.SendData(data, sequenceId);
return _packetStream.Where(p => p.SequenceId == sequenceId)
.Take(1)
.Timeout(timeout)
.Subscribe(obs)
})
.Retry(retries);
}
Putting the Retry operator at the end causes, the Create observable to be retried if it times out. As an aside, there are overloads of Timeout that allow you to pass in another observable sequence to use in the case of timeout. You can use this overload along with Observable.Throw to provide your own exception in case of a timeout if desired, such as to provide an alternate error message.
Note that this code does not send data until you subscribe and does not block until the result is returned or the timeout is reached but does let you cancel further retries by Disposing the subscription. This code also does not prevent you from sending multiple packets at the same time. If you must block, you can do something like this:
var response = SendData(/* arguments */);
response.Do(success, fail).StartWith(null).ToTask().Wait();
If you are using C# 5 and calling this within an async method, you can await the observable.
What I got so far is this:
private void WaitForAnswer(byte sequenceId, int timeout, Action<Packet> success, Action fail, int retriesLeft)
{
_packetStream.Where(p => p.GetSequence() == sequenceId)
.Take(1)
.Timeout(TimeSpan.FromMilliseconds(timeout))
.Subscribe(success,
_ => {
if (retriesLeft > 0) WaitForAnswer(sequenceId, timeout, success, fail, --retriesLeft);
else fail();
},
() => { });
}
I'm not quite sure though, if this solution correctly disposes of subscription.