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);
});
}
Related
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.
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);
}
I need to recollect some data calling to a method is connecting to a webservice.
problem: Imagine I need to update the content text of a label control according to this remote gathered information. Until all this data is recollected I'm not going to be able to show the label.
desired: I'd like to first show the label with a default text, and as I'm receiving this information I want to update the label content (please, don't take this description as a sucked code, I'm trying to brief my real situation).
I'd like to create an observable sequence of these methods. Nevertheless, these method have not the same signature. For example:
int GetInt() {
return service.GetInt();
}
string GetString() {
return service.GetString();
}
string GetString2 {
return service.GetString2();
}
These methods are not async.
Is it possible to create an observable sequence of these methods?
How could I create it?
Nevertheless, which's the best alternative to achieve my goal?
Creating custom observable sequences can be achieved with the Observable.Create. An example using your requirements is shown below:
private int GetInt()
{
Thread.Sleep(1000);
return 1;
}
private string GetString()
{
Thread.Sleep(1000);
return "Hello";
}
private string GetString2()
{
Thread.Sleep(2000);
return "World!";
}
private IObservable<string> RetrieveContent()
{
return Observable.Create<string>(
observer =>
{
observer.OnNext("Default Text");
int value = GetInt();
observer.OnNext($"Got value {value}. Getting string...");
string string1 = GetString();
observer.OnNext($"Got string {string1}. Getting second string...");
string string2 = GetString2();
observer.OnNext(string2);
observer.OnCompleted();
return Disposable.Empty;
}
);
}
Note how I have emulated network delay by introducing a Thread.Sleep call into each of the GetXXX methods. In order to ensure your UI doesn't hang when subscribing to this observable, you should subscribe as follows:
IDisposable subscription = RetrieveContent()
.SubscribeOn(TaskPoolScheduler.Default)
.ObserveOn(DispatcherScheduler.Current)
.Subscribe(text => Label = text);
This code uses the .SubscribeOn(TaskPoolScheduler.Default) extension method to use a TaskPool thread to start the observable sequence and will be blocked by the calls the Thread.Sleep but, as this is not the UI thread, your UI will remain responsive. Then, to ensure we update the UI on the UI thread, we use the ".ObserveOn(DispatcherScheduler.Current)" to invoke the updates onto the UI thread before setting the (data bound) Label property.
Hope this is what you were looking for, but leave a comment if not and I'll try to help further.
I would look at creating a wrapper class for your service to expose the values as separate observables.
So, start with a service interface:
public interface IService
{
int GetInt();
string GetString();
string GetString2();
}
...and then you write ServiceWrapper:
public class ServiceWrapper : IService
{
private IService service;
private Subject<int> subjectGetInt = new Subject<int>();
private Subject<string> subjectGetString = new Subject<string>();
private Subject<string> subjectGetString2 = new Subject<string>();
public ServiceWrapper(IService service)
{
this.service = service;
}
public int GetInt()
{
var value = service.GetInt();
this.subjectGetInt.OnNext(value);
return value;
}
public IObservable<int> GetInts()
{
return this.subjectGetInt.AsObservable();
}
public string GetString()
{
var value = service.GetString();
this.subjectGetString.OnNext(value);
return value;
}
public IObservable<string> GetStrings()
{
return this.subjectGetString.AsObservable();
}
public string GetString2()
{
var value = service.GetString2();
this.subjectGetString2.OnNext(value);
return value;
}
public IObservable<string> GetString2s()
{
return this.subjectGetString2.AsObservable();
}
}
Now, assuming that you current service is called Service, you would write this code to set things up:
IService service = new Service();
ServiceWrapper wrapped = new ServiceWrapper(service); // Still an `IService`
var subscription =
Observable
.Merge(
wrapped.GetInts().Select(x => x.ToString()),
wrapped.GetStrings(),
wrapped.GetString2s())
.Subscribe(x => label.Text = x);
IService wrappedService = wrapped;
Now pass wrappedService instead of service to your code. It's still calling the underlying service code so no need for a re-write, yet you still are getting the observables that you want.
This is effectively a gang of four decorator pattern.
The program below attempts to print out words with their respective lengths. It erroneously reports that cat has 6 letters. As I examine the log, it looks like the length of a specific word is emitted BEFORE the word it is based upon is emitted. How is this possible? The length observable is defined as word.select(i=>i.Length) so I don't see how it could produce a result before the word arrives. At first I thought this might be a bug in my logging code, but the behavior of Observable.WithLatestFrom reinforces my belief that something weird is going on here.
Log results:
0001report.Subscribe()
0002first.Subscribe()
0003second.Subscribe()
0002first.OnNext(3)
0003second.OnNext(cat)
0002first.OnNext(6)
0001report.OnNext({ Word = cat, Length = 6 })
0003second.OnNext(donkey)
The program:
static void Main(string[] args) {
ILogger logger = new DelegateLogger(Console.WriteLine);
Subject<string> word = new Subject<string>();
IObservable<int> length = word.Select(i => i.Length);
var report = Observable
.WithLatestFrom(
length.Log(logger, "first"),
word.Log(logger, "second"),
(l, w) => new { Word = w, Length = l })
.Log(logger,"report");
report.Subscribe();
word.OnNext("cat");
word.OnNext("donkey");
Console.ReadLine();
}
public interface ILogger
{
void Log(string input);
}
public class DelegateLogger : ILogger
{
Action<string> _printer;
public DelegateLogger(Action<string> printer) {
_printer = printer;
}
public void Log(string input) => _printer(input);
}
public static class ObservableLoggingExtensions
{
private static int _index = 0;
public static IObservable<T> Log<T>(this IObservable<T> source, ILogger logger, string name) {
return Observable.Create<T>(o => {
var index = Interlocked.Increment(ref _index);
var label = $"{index:0000}{name}";
logger.Log($"{label}.Subscribe()");
var disposed = Disposable.Create(() => logger.Log($"{label}.Dispose()"));
var subscription = source
.Do(
x => logger.Log($"{label}.OnNext({x?.ToString() ?? "null"})"),
ex => logger.Log($"{label}.OnError({ex})"),
() => logger.Log($"{label}.OnCompleted()")
)
.Subscribe(o);
return new CompositeDisposable(subscription, disposed);
});
}
}
I think I know what is going on. There are two subscriptions on word (1. length 2. WithLatestFrom), and one subscription on length (1. WithLatestFrom)
When a word is emitted, a synchronous callback process starts that passes it to the first subscriber (length), which calculates a value, that is passed to its subscriber, WithLatestFrom. Next, WithLatestFrom receives the word that generated the calculated length. So WithLatestFrom receives the length BEFORE the word, not the other way around. That's why the report isn't giving me the results I expected.
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);