2

As described in my original question (see Correlate interdependent Event Streams with RX.Net) I have an RX.net event stream that shall only call the observer's OnNext method as long as a certain other event is not triggered (basically 'Handle Change-* Events as long as the system is connected, pause while disconnected and re-start handling of the Change-* events once the system has re-connected).

However, while this works smoothly with new events, how would I cancel / signal cancellation to ongoing .OnNext() calls?

Community
  • 1
  • 1
Jörg Battermann
  • 4,044
  • 5
  • 42
  • 79
  • For clarification, the handling method is async/await-able and already takes a CancellationToken as parameter.. maybe it is possible to combine RX & this async method. – Jörg Battermann Nov 03 '14 at 14:58
  • Can you post some code? What are you passing in for a `CancellationToken` right now? – Brandon Nov 03 '14 at 15:01
  • @Brandon will do later today, currently on the run and the project is on my machine at home. Right now I am merely handing in a default(CancellationToken) / CancellationToken.None as I was playing with but not convinced about using and external CancellationTokenSource field (that I'd trigger in the original Stream and re-create thereafter). – Jörg Battermann Nov 03 '14 at 15:04

2 Answers2

1

Since your observer is already written to accept a CancellationToken, we can just modify your Rx stream to supply one along with the event data. We'll use the Rx CancellationDisposable that we will dispose of whenever the stream is unsubscribed.

// Converts into a stream that supplies a `CancellationToken` that will be cancelled when the stream is unsubscribed
public static IObservable<Tuple<CancellationToken, T>> CancelOnUnsubscribe<T>(this IObservable<T> source)
{
    return Observable.Using(
        () => new CancellationDisposable(),
        cts => source.Select(item => Tuple.Create(cts.Token, item)));
}

Putting this together with the solution from the other question:

DataSourceLoaded
    .SelectMany(_ => DataSourceFieldChanged
        .Throttle(x)
        .CancelOnUnsubscribe()
        .TakeUntil(DataSourceLoaded))
    .Subscribe(c => handler(c.Item1, c.Item2));

When the TakeUntil clause is triggered, it will unsubscribe from the CancelOnUnsubscribe observable, which will in turn dispose of the CancellationDisposable and cause the token to be cancelled. Your observer can watch this token and stop its work when this happens.

Brandon
  • 38,310
  • 8
  • 82
  • 87
  • Thanks for the solution! I found your answer to a similiar Question (http://stackoverflow.com/questions/18477018/rx-and-tasks-cancel-running-task-when-new-task-is-spawned?rq=1) a bit more elegant to read though and combined it like this: https://gist.github.com/jbattermann/914d48f4011f0842a03b – Jörg Battermann Nov 05 '14 at 21:05
  • Updated the gist & it does work completely now. Thanks Brandon, also for the hint regarding CancellationDisposable - didn't know that one, too. – Jörg Battermann Nov 05 '14 at 21:36
  • Ah yeah that was for a slightly different problem than what you've posted here. Your gist (and that solution) cancel the current worker task and start a new one whenever a new `DataSourceFieldChanged` event arrives. Sounds like that's actually what you wanted though so good deal :) – Brandon Nov 05 '14 at 22:32
0

There's an async overload of SelectMany, though admittedly if similar overloads of Do existed it would be more semantically appropriate.

var subscription = 
  (from _ in DataSourceLoaded
   from __ in DataSourceFieldChanged
     .Throttle(x)
     .SelectMany(DataSourceFieldChangedAsync)
     .TakeUntil(DataSourceUnloaded)
   select Unit.Default);
  .Subscribe();  // Subscribing for side effects only.

...

async Task<Unit> DataSourceFieldChangedAsync(Field value, CancellationToken cancel);

This is nice because it ties cancellation to the subscription as well.

Calling either

subscription.Dispose()

or

DataSourceUnloaded.OnNext(x);

will cause the CancellationToken to be cancelled.

Dave Sexton
  • 2,562
  • 1
  • 17
  • 26
  • I created a work item. Alternative approaches are currently being discussed: https://github.com/Reactive-Extensions/Rx.NET/issues/63#issuecomment-61631595 – Dave Sexton Nov 04 '14 at 13:22
  • Thansk Dave. I've taken your and Brandon's solutions (more precisely, Brandon's answer to basically the same but earlier asked SO Question) and combined them into this: https://gist.github.com/jbattermann/914d48f4011f0842a03b – Jörg Battermann Nov 05 '14 at 21:03
  • This works only 'almost' correctly though: IF a DataSourceFieldChanged is still being handled asynchronously and a DataSourceUnloaded happens in the meantime, the later would not trigger the cancellationToken for the WorkerTask(..) method - for obvious reasons, but not entirely "there", yet. – Jörg Battermann Nov 05 '14 at 21:14
  • Alright, had a closer look at the logical arrangement of the Select/Switch and TakeUntil statements and the current Gist revision works entirely as expected \o/ – Jörg Battermann Nov 05 '14 at 21:35