I am using ReactiveUI for a UWP app and have two commands CommandA and CommandB. CommandA when invoked attempts to make changes to the hardware. CommandB when invoked reads the hardware and provides the latest value.
I would like to invoke the CommandA (with the parameter as the CombBox value) when a ComboBox value is changed.
After the execution of CommandA I would like to invoke CommandB repeatedly till I get the value that is same as the one selected in ComboBox or if a timeout occurs. On timeout an error should be displayed.
To check that the CommandA is finished executing, I wrote the following code for [1]
this.WhenAnyValue(x => x.ComboBoxAValue)
.InvokeCommand(CommandA);
CommandA.IsExecuting
.Buffer(2,1)
.Where(t => t[0] == true && t[1] == false)
.Select( x=> Unit.Default)
.InvokeCommand(CommandB) // This statement would attempt to invoke CommandB only once
I am not sure how to do [2].
I'm conviced that exists a better solution, but here you have an approach:
public class MyCoolViewModel : ReactiveObject
{
private readonly Subject<Unit> _ticksToInvokeB;
private readonly ObservableAsPropertyHelper<bool> _commandAExecutedCorrectly;
public bool CommandAExecutedCorrectly => _commandAExecutedCorrectly.Value;
public ReactiveCommand<Unit, bool> CommandA { get; set; }
public ReactiveCommand<Unit, bool> CommandB { get; set; }
private string _comboBoxValue;
public string ComboBoxValue
{
get => _comboBoxValue;
set => this.RaiseAndSetIfChanged(ref _comboBoxValue, value);
}
public MyCoolViewModel()
{
//Subject implements IObservable<T> and IObserver<T>, also alow us to tick values to its observable
_ticksToInvokeB = new Subject<Unit>();
CommandA = ReactiveCommand.Create<Unit,bool>( _ =>
{
Console.WriteLine("Command A");
return true;
});
CommandB = ReactiveCommand.CreateFromTask<Unit,bool>( async _ =>
{
await Task.Delay(3000);
var isTheSame = DateTime.Now.Second % 2 == 0;
Console.WriteLine($"CommandB: is the same: {isTheSame}");
if(!isTheSame)//if the value is not the same, tick a new unit, since we ticked a new value, CommandA will be executed
_ticksToInvokeB.OnNext(Unit.Default);
return isTheSame;
});
CommandA//We just send commandA execution to an OAPH
.ToProperty(this, x => x.CommandAExecutedCorrectly, out _commandAExecutedCorrectly);
this.WhenAnyValue(x => x.ComboBoxValue)
.Skip(1) //this is because ComboBoxValue has a initial value (null) so we ignore it
.Select(_ => Unit.Default) //When it changes simply project an Unit
.InvokeCommand(CommandA);//Inke CommandA
this.WhenAnyValue(x => x.CommandAExecutedCorrectly)//When changes maded CommandA will set ChangesMaded to true
.Where(b => b) // only when Command A gets executed correctly
.Do(b => TickUnit()) // Tick a new unit
.Subscribe();
_ticksToInvokeB
.Throttle(TimeSpan.FromMilliseconds(200))//delay a little bit the next value
.InvokeCommand(CommandB);//invokes CommandB
}
private void TickUnit()
{
Console.WriteLine("Ticking new value");
_ticksToInvokeB.OnNext(Unit.Default);
}
}
Let me know if it helps you.
Regards.
First, instead of the buffer technique you used for detecting a completed command, I would do something like this. And instead of worrying about creating a command for CommandB, I would just execute it as a method. You can still use a command if you want, but I'll just be using an async call in this example. I'm using ExecuteUntilItYieldsTheSelectedComboBoxValue to continuously execute your CommandB logic in a loop until a matching value is found. It utilizes Observable.Create so you can control when OnNext is triggered. And you can tag on a Timeout to it and handle it in the Subscribe extension.
CommandA.IsExecuting
// IsExecuting has an initial value of false. We can skip that first value
.Skip(1)
// Filter until the executing state becomes false.
.Where(isExecuting => !isExecuting)
// Start an inner observable for your "CommandB" logic.
.Select(_ => ExecuteUntilItYieldsTheSelectedComboBoxValue())
// Whenever CommandA is invoked, dispose of the last inner observable subscription and resubscribe.
.Switch()
.Subscribe(
_ => Console.WriteLine("OnNext"),
ex => [display error message]);
...
private IObservable<Unit> ExecuteUntilItYieldsTheSelectedComboBoxValue()
{
return Observable
.Create<Unit>(
async o =>
{
int randNum = -1;
do
{
randNum = await GetRandomNumberAsync();
} while(randNum != ComboBoxValue);
o.OnNext(Unit.Default);
o.OnCompleted();
return Disposable.Empty;
})
.Timeout(TimeSpan.FromSeconds(3));
}
Update
Based on what Enigmativity pointed out, about needing to return something better than Disposable.Empty (to cancel any in-progress async task and break out of the loop), I'm changing the ExecuteUntilItYieldsTheSelectedComboBoxValue method to be the following:
private IObservable<Unit> ExecuteUntilItYieldsTheSelectedComboBoxValue()
{
return Observable
// Call our async method and pass in a cancellation token.
.FromAsync(ct => GetRandomNumberAsync(ct))
// Keep generating random numbers.
.Repeat()
// Until we find one that matches the ComboBoxValue.
.Where(x => x == ComboBoxValue)
// Just take the first one so this inner observable will complete.
.Take(1)
.Select(_ => Unit.Default)
.Timeout(TimeSpan.FromSeconds(3));
}
Note that it's still possible to make the Observable.Create method work correctly, but this edited solution is cleaner and less error prone. Let me know if you have any questions.
Related
Given this setup,
public static class NotifyPropertyChangedExtensions
{
public static IObservable<EventPattern<PropertyChangedEventArgs>> WhenPropertyChanged(this INotifyPropertyChanged source)
{
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
ev => source.PropertyChanged += ev,
ev => source.PropertyChanged -= ev);
}
}
public class Node : INotifyPropertyChanged
{
public bool IsChecked
{
get => _isChecked;
set
{
if (_isChecked != value)
{
_isChecked = value;
OnPropertyChanged();
}
}
}
}
nodes.Values.Select(node => node.WhenPropertyChanged())
.Merge()
.Where(x => x.EventArgs.PropertyName == "IsChecked")
.Throttle(TimeSpan.FromMilliseconds(200))
.Subscribe(x =>
{
// do somethings
});
why does this not fire any events when I call?
nodeX.IsChecked = true;
immediately after setting up the subscription but works afterwards?
You do realize that is the intended behavior of the Throttle operator:
only emit an item from an Observable if a particular timespan has passed without it emitting another item
(source)
So in your case, since you perform the Merge before applying the Throttle operator, an item is emitted after a change to IsChecked and then after 200 ms.
In rx, how do you handle the need to reuse an object instance in one step in the next step? For example, I need to get a context in the ORM to then act upon. Async/Await is in the syntax below:
public async Task<IList<string>> Delete(IList<string> ids)
{
var context = await _contextFactory.CreateContext();
context.Set<T>().RemoveRange(
context.Set<T>().Where(item => ids.Contains(item.Id)));
return ids;
}
An Observable version is
public IObservable<string> DeleteObservable(IList<string> ids)
{
return ids.ToObservable()
.Select(i =>
{
var context = await _contextFactory.CreateContext();
context.Set<T>().RemoveRange(
context.Set<T>().Where(item => item.Id == id));
return id;
});
}
However, I don't want to create a new context every time I delete an item. I want to create a context and then reuse it in the select. How should I do that?
Yes, in this example it would be best to also buffer and submit the ids together, but this was just an example for my question. I hope that part is not distracting.
The more idiomatic way of doing it is like this:
public IObservable<string> DeleteObservable(IList<string> ids)
{
return Observable.Using(
async () => await _contextFactory.CreateContext(),
context =>
ids.ToObservable().Select(i =>
{
context.Set<T>().RemoveRange(context.Set<T>().Where(item => item.Id == i));
return i;
}));
}
The Observable.Using method creates a disposable resource that gets disposed when the subscription to the observable closes.
The only problem with this is that the statement context.Set<T>().RemoveRange(context.Set<T>().Where(item => item.Id == i)); just shouldn't be inside an observable like that. Rx is about queries. Any changes should be made in a .Subscribe method.
What are you trying to achieve?
I think I got it and the answer keeps ended up being 'SelectMany'. I guess I'm still getting used to these operators.
public IObservable<string> DeleteObservable(IList<string> ids)
{
return Observable
.Return(_contextFactory)
.SelectMany(factory => factory.CreateContext())
.Zip(ids.ToObservable(), (dbContext, entityId) =>
{
dbContext.Set<T>().RemoveRange(
dbContext.Set<T>().Where(item => item.Id == entityId));
return entityId;
});
}
I have a source stream and usually want to emit items as they arrive. But there is another observable - let's call it the "gate". When the gate is closed, the source items should buffer and be released only when the gate is opened.
I've been able to write a function to do this but it seems more complicated than it needs to be. I had to use the Observable.Create method. I assume there is a way to accomplish my goal using just a few lines of more functional code using the Delay or Buffer methods but I can't figure out how. Delay seems especially promising but I can't figure out how to sometimes delay and sometimes allow everything through immediately (a zero delay). Likewise I thought I could use Buffer followed by SelectMany; when the gate is open I'd have buffers of length 1 and when the gate is closed I'd have longer ones, but again I couldn't figure out how to make it work.
Here is what I've built that works with all my tests:
/// <summary>
/// Returns every item in <paramref name="source"/> in the order it was emitted, but starts
/// caching/buffering items when <paramref name="delay"/> emits true, and releases them when
/// <paramref name="delay"/> emits false.
/// </summary>
/// <param name="delay">
/// Functions as "gate" to start and stop the emitting of items. The gate is opened when true
/// and closed when false. The gate is open by default.
/// </param>
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay) =>
Observable.Create<T>(obs =>
{
ImmutableList<T> buffer = ImmutableList<T>.Empty;
bool isDelayed = false;
var conditionSubscription =
delay
.DistinctUntilChanged()
.Subscribe(i =>
{
isDelayed = i;
if (isDelayed == false)
{
foreach (var j in buffer)
{
obs.OnNext(j);
}
buffer = ImmutableList<T>.Empty;
}
});
var sourceSubscription =
source
.Subscribe(i =>
{
if (isDelayed)
{
buffer = buffer.Add(i);
}
else
{
obs.OnNext(i);
}
});
return new CompositeDisposable(sourceSubscription, conditionSubscription);
});
Here is another option that passes the tests. It is pretty concise but does not use the Delay or Buffer methods; I need to do the delaying/buffering manually.
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay) =>
delay
.StartWith(false)
.DistinctUntilChanged()
.CombineLatest(source, (d, i) => new { IsDelayed = d, Item = i })
.Scan(
seed: new { Items = ImmutableList<T>.Empty, IsDelayed = false },
accumulator: (sum, next) => new
{
Items = (next.IsDelayed != sum.IsDelayed) ?
(next.IsDelayed ? sum.Items.Clear() : sum.Items) :
(sum.IsDelayed ? sum.Items.Add(next.Item) : sum.Items.Clear().Add(next.Item)),
IsDelayed = next.IsDelayed
})
.Where(i => !i.IsDelayed)
.SelectMany(i => i.Items);
These are my tests:
[DataTestMethod]
[DataRow("3-a 6-b 9-c", "1-f", "3-a 6-b 9-c", DisplayName = "Start with explicit no_delay, emit all future items")]
[DataRow("3-a 6-b 9-c", "1-f 2-f", "3-a 6-b 9-c", DisplayName = "Start with explicit no_delay+no_delay, emit all future items")]
[DataRow("3-a 6-b 9-c", "1-t", "", DisplayName = "Start with explicit delay, emit nothing")]
[DataRow("3-a 6-b 9-c", "1-t 2-t", "", DisplayName = "Start with explicit delay+delay, emit nothing")]
[DataRow("3-a 6-b 9-c", "5-t 10-f", "3-a 10-b 10-c", DisplayName = "When delay is removed, all cached items are emitted in order")]
[DataRow("3-a 6-b 9-c 12-d", "5-t 10-f", "3-a 10-b 10-c 12-d", DisplayName = "When delay is removed, all cached items are emitted in order")]
public void DelayWhile(string source, string isDelayed, string expectedOutput)
{
(long time, string value) ParseEvent(string e)
{
var parts = e.Split('-');
long time = long.Parse(parts[0]);
string val = parts[1];
return (time, val);
}
IEnumerable<(long time, string value)> ParseEvents(string s) => s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(ParseEvent);
var scheduler = new TestScheduler();
var sourceEvents = ParseEvents(source).Select(i => OnNext(i.time, i.value)).ToArray();
var sourceStream = scheduler.CreateHotObservable(sourceEvents);
var isDelayedEvents = ParseEvents(isDelayed).Select(i => OnNext(i.time, i.value == "t")).ToArray();
var isDelayedStream = scheduler.CreateHotObservable(isDelayedEvents);
var expected = ParseEvents(expectedOutput).Select(i => OnNext(i.time, i.value)).ToArray();
var obs = scheduler.CreateObserver<string>();
var result = sourceStream.DelayWhile(isDelayedStream);
result.Subscribe(obs);
scheduler.AdvanceTo(long.MaxValue);
ReactiveAssert.AreElementsEqual(expected, obs.Messages);
}
[TestMethod]
public void DelayWhile_SubscribeToSourceObservablesOnlyOnce()
{
var scheduler = new TestScheduler();
var source = scheduler.CreateHotObservable<int>();
var delay = scheduler.CreateHotObservable<bool>();
// No subscriptions until subscribe
var result = source.DelayWhile(delay);
Assert.AreEqual(0, source.ActiveSubscriptions());
Assert.AreEqual(0, delay.ActiveSubscriptions());
// Subscribe once to each
var obs = scheduler.CreateObserver<int>();
var sub = result.Subscribe(obs);
Assert.AreEqual(1, source.ActiveSubscriptions());
Assert.AreEqual(1, delay.ActiveSubscriptions());
// Dispose subscriptions when subscription is disposed
sub.Dispose();
Assert.AreEqual(0, source.ActiveSubscriptions());
Assert.AreEqual(0, delay.ActiveSubscriptions());
}
[TestMethod]
public void DelayWhile_WhenSubscribeWithNoDelay_EmitCurrentValue()
{
var source = new BehaviorSubject<int>(1);
var emittedValues = new List<int>();
source.DelayWhile(Observable.Return(false)).Subscribe(i => emittedValues.Add(i));
Assert.AreEqual(1, emittedValues.Single());
}
// Subscription timing issue?
[TestMethod]
public void DelayWhile_WhenSubscribeWithDelay_EmitNothing()
{
var source = new BehaviorSubject<int>(1);
var emittedValues = new List<int>();
source.DelayWhile(Observable.Return(true)).Subscribe(i => emittedValues.Add(i));
Assert.AreEqual(0, emittedValues.Count);
}
[TestMethod]
public void DelayWhile_CoreScenario()
{
var source = new BehaviorSubject<int>(1);
var delay = new BehaviorSubject<bool>(false);
var emittedValues = new List<int>();
// Since no delay when subscribing, emit value
source.DelayWhile(delay).Subscribe(i => emittedValues.Add(i));
Assert.AreEqual(1, emittedValues.Single());
// Turn on delay and buffer up a few; nothing emitted
delay.OnNext(true);
source.OnNext(2);
source.OnNext(3);
Assert.AreEqual(1, emittedValues.Single());
// Turn off delay; should release the buffered items
delay.OnNext(false);
Assert.IsTrue(emittedValues.SequenceEqual(new int[] { 1, 2, 3 }));
}
EDIT: I forgot about the problems you'll run into with Join and Join-based operators (like WithLatestFrom) when having two cold observables. Needless to say, that criticism mentioned below about lack of transactions is more apparent than ever.
I would recommend this, which is more like my original solution but using the Delay overload. It passes all tests except DelayWhile_WhenSubscribeWithDelay_EmitNothing. To get around that, I would create an overload that would accept a starting default value:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay, bool isGateClosedToStart)
{
return source.Publish(_source => delay
.DistinctUntilChanged()
.StartWith(isGateClosedToStart)
.Publish(_delay => _delay
.Select(isGateClosed => isGateClosed
? _source.TakeUntil(_delay).Delay(_ => _delay)
: _source.TakeUntil(_delay)
)
.Merge()
)
);
}
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return DelayWhile(source, delay, false);
}
Old answer:
I read a book recently criticizing Rx for not supporting transactions, and my first try at solving this would be a great example why:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return source.Publish(_source => delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => _delay
.Select(isGateClosed => isGateClosed
? _source.Buffer(_delay).SelectMany(l => l)
: _source)
.Switch()
)
);
}
That should work, except there's too many things relying on the delay observable, and the subscription order matters: In this case the Switch switches before the Buffer ends, so nothing ends up coming out when the delay gate is closed.
This can be fixed as follows:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return source.Publish(_source => delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => _delay
.Select(isGateClosed => isGateClosed
? _source.TakeUntil(_delay).Buffer(_delay).SelectMany(l => l)
: _source.TakeUntil(_delay)
)
.Merge()
)
);
}
My next try passed all your tests, and uses your desired Observable.Delay overload as well:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => source
.Join(_delay,
s => Observable.Empty<Unit>(),
d => _delay,
(item, isGateClosed) => isGateClosed
? Observable.Return(item).Delay(, _ => _delay)
: Observable.Return(item)
)
.Merge()
);
}
The Join could be reduced to a WithLatestFrom like this:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => source
.WithLatestFrom(_delay,
(item, isGateClosed) => isGateClosed
? Observable.Return(item).Delay(_ => _delay)
: Observable.Return(item)
)
.Merge()
);
}
A proposed concise answer. It looks like it should work but it doesn't pass all the tests.
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
source = source.Publish().RefCount();
delay = delay.Publish().RefCount();
var delayRemoved = delay.Where(i => i == false);
var sourceWhenNoDelay = source.WithLatestFrom(delay.StartWith(false), (s, d) => d).Where(i => !i);
return
source
.Buffer(bufferClosingSelector: () => delayRemoved.Merge(sourceWhenNoDelay))
.SelectMany(i => i);
}
Am using reactive extensions in C# to perform some calculations. Here is how my code looks like so far. I have tried to wrap the code around so that I can show progress while to executing a series of tasks within my Calculate method
Here is the observable
IObservable<ResultWithProgress<SampleResult>> Calculate(){
return Observable.Create<ResultWithProgress<SampleResult>>(obs => {
var someTask = DoSomeTask1();
obs.OnNext(new ResultWithProgress(){Progress = 25, ProgressText ="Completed Task1"});
var someOtherTask = DoSomeMoreTask();
obs.OnNext(new ResultWithProgress(){Progress = 50, ProgressText ="Completed Task2"});
var calcResult = DoSomeMoreTask2();
obs.OnNext(new ResultWithProgress(){Progress = 75, ProgressText = "Completed Task3"});
var calcResult = FinalCalc();
obs.OnNext(new ResultWithProgress(){Progress = 100, ProgressText ="Completed Task4", Result = calcResult});
obs.OnCompleted();
}
}
Result Class wrapping progress and result
class ResultWithProgress<T>{
public int Progress {get; set;}
public Result T {get; set;}
public string ProgressText {get; set;}
}
Result object which contains the final result
class SampleResult{}
Usage:
Calculate().Subscribe(resultWithProgress => {
if(resultWithProgress.Result == null) //Show progress using resultWithProgress.Progress
else // get the result
})
I somehow feel that this might not the best way to do it. It feels that creating ResultWithProgress object many times without the Result seems like a code smell, especially if I have more than 10 tasks that I want to do within my Calculate()
I would appreciate it if you can give me any pointers on how to use this or am I approaching this problem wrongly?
This answer uses the same principles Enigmativity's answer discusses.
This version uses the async overload of Create.
It also makes use of the .NET 4.5 IProgress instead of a raw Action<T> to report progress.
struct CalculationProgress
{
public int Progress { get; private set; }
public string ProgressText { get; private set; }
public CalculationProgress(int progress, string progressText)
: this()
{
Progress = progress;
ProgressText = progressText;
}
}
public IObservable<Result> Calculate(IProgress<CalculationProgress> progress)
{
return Observable.Create<Result>((observer, cancellationToken) =>
{
// run the work on a background thread
// so we do not block the subscriber
// and thus the subscriber has a chance
// to unsubscribe (and cancel the work if desired)
return Task.Run(() =>
{
DoSomeTask1();
cancellationToken.ThrowIfCancellationRequested();
progress.Report(new CalculationProgress(25, "First task"));
DoSomeTask2();
cancellationToken.ThrowIfCancellationRequested();
progress.Report(new CalculationProgress(50, "Second task"));
DoSomeTask3();
cancellationToken.ThrowIfCancellationRequested();
progress.Report(new CalculationProgress(75, "third task"));
var result = DoFinalCalculation();
cancellationToken.ThrowIfCancellationRequested();
progress.Report(new CalculationProgress(100, "final task"));
observer.OnNext(result);
}, cancellationToken);
});
}
It took me some time to actually get your code to run. There were numerous syntax errors, but most importantly your Observable.Create did not have a return value.
Observable.Create should create an observable that the obs variable subscribes to and you return that IDisposable. That's so a subscriber can terminate the observable before it has completed.
Your observable directly interacts with the obs and finally calls obs.OnComplete() before the Observable.Create is completed. This means that there is no opportunity for the calling subscriber to terminate the computation because it has completed before the subscription has finished!
What you need is a way to build an observable within the Observable.Create to make it behave properly.
Now, since you are trying to return progress during your computation you are expecting side-effects. So it is easier to inject state at the beginning and just have a pure observable otherwise.
Here's how I might go about doing this.
First I change the signature of Calculate to become:
IObservable<string> Calculate(Action<ResultWithProgress<string>> progress)
Now I am injecting an action that I can use to report on my progress.
Here's how the call to Calculate might look:
Calculate(rwp => Console.WriteLine(rwp)).Subscribe(result => { });
Now here's the full Calculate method:
public IObservable<string> Calculate(Action<ResultWithProgress<string>> progress)
{
return Observable.Create<string>(obs =>
{
// This action just removes duplication from the query below
// and has the purpose of safely calling `progress`
Action<int, string, string> report = (pv, r, pt) =>
{
var p = progress;
if (p != null)
{
p(new ResultWithProgress<string>()
{
Progress = pv,
Result = r,
ProgressText = pt,
});
}
};
var query =
from someTask in Observable.Start(() => DoSomeTask1())
.Do(x => report(25, x, "Completed Task1"))
from someOtherTask in Observable.Start(() => DoSomeMoreTask())
.Do(x => report(50, x, "Completed Task2"))
from calcResultX in Observable.Start(() => DoSomeMoreTask2())
.Do(x => report(75, x, "Completed Task3"))
from calcResult in Observable.Start(() => DoSomeTask1())
.Do(x => report(100, x, "Completed Task4"))
select calcResult;
return query.Subscribe(obs);
});
}
I'll create an observable (through a variety of means) and return it to interested parties, but when they're done listening, I'd like to tear down the observable so it doesn't continue consuming resources. Another way to think of it as creating topics in a pub sub system. When no one is subscribed to a topic any more, you don't want to hold the topic and its filtering around anymore.
Rx already has an operator to suit your needs - well two actually - Publish & RefCount.
Here's how to use them:
IObservable xs = ...
var rxs = xs.Publish().RefCount();
var sub1 = rxs.Subscribe(x => { });
var sub2 = rxs.Subscribe(x => { });
//later
sub1.Dispose();
//later
sub2.Dispose();
//The underlying subscription to `xs` is now disposed of.
Simple.
If I have understood your question you want to create the observable such that when all subscribers have disposed their subscription i.e there is no more subscriber, then you want to execute a clean up function which will stop the observable from production further values.
If this is what you want then you can do something like below:
//Wrap a disposable
public class WrapDisposable : IDisposable
{
IDisposable disp;
Action act;
public WrapDisposable(IDisposable _disp, Action _act)
{
disp = _disp;
act = _act;
}
void IDisposable.Dispose()
{
act();
disp.Dispose();
}
}
//Observable that we want to clean up after all subs are done
public static IObservable<long> GenerateObs(out Action cleanup)
{
cleanup = () =>
{
Console.WriteLine("All subscribers are done. Do clean up");
};
return Observable.Interval(TimeSpan.FromSeconds(1));
}
//Wrap the observable
public static IObservable<T> WrapToClean<T>(IObservable<T> obs, Action onAllDone)
{
int count = 0;
return Observable.CreateWithDisposable<T>(ob =>
{
var disp = obs.Subscribe(ob);
Interlocked.Increment(ref count);
return new WrapDisposable(disp,() =>
{
if (Interlocked.Decrement(ref count) == 0)
{
onAllDone();
}
});
});
}
//Usage example:
Action cleanup;
var obs = GenerateObs(out cleanup);
var newObs = WrapToClean(obs, cleanup);
newObs.Take(6).Subscribe(Console.WriteLine);
newObs.Take(5).Subscribe(Console.WriteLine);