4

I'm trying to understand when is a good time to return a disposable in the function passed to Observable.Create vs just disposing any resources through scope by a using statement.

Is returning the disposable more for cases where the Observable is an infinite stream? Even if so I don't understand how the using block won't still dispose the resource even if the stream is closed prematurely

JFord
  • 126
  • 1
  • 9
  • Are you concerned about the possible consequences of not disposing the disposable subscription? – Theodor Zoulias Nov 30 '19 at 01:49
  • @TheodorZoulias This is more a question of lazy-vs-eager cleanup perhaps? I just don't really see any advice / best practices online that explains when to do which and actual reasons for it. – JFord Nov 30 '19 at 05:36
  • 1
    I think that disposables are used in RX solely for unsubscription purposes, not for releasing unmanaged resources. – Theodor Zoulias Nov 30 '19 at 06:49
  • @TheodorZoulias - There can be managed resources - especially for time-based observables. – Enigmativity Dec 05 '19 at 11:48
  • You should never ever use `using` in a disposable pipeline. Always use `Observable.Using`. If I understand your question you're writing an `Observable.Create` where you're calling `o.OnNext` and `o.OnCompleted` inside a `using` statement inside the `Create`? If so, stop that as it will cause your observable to run to completion before the subscription is finished. – Enigmativity Dec 05 '19 at 11:52
  • @Enigmativity Yeah I had been calling `o.OnNext` for each item with a using statement surrounding the iterative code and then called `o.OnComplete` outside of that. How come this would make the observable complete before the subscription completes? I'm not sending data that would be cleaned up by the disposal of the resource to the `OnNext` method. – JFord Dec 05 '19 at 16:36
  • @JFord - Try running this code: `Observable.Create(o => { Thread.Sleep(5000); o.OnNext(42); o.OnCompleted(); return Disposable.Empty; }).Subscribe(Console.WriteLine); Console.WriteLine("Done.");`. The `42` gets output before the `Done.`. That's just how `Observable.Create` works. – Enigmativity Dec 05 '19 at 22:52
  • @Enigmativity That's only because its running on the same thread. If you specified a different scheduler it might not necessarily run like that. – JFord Dec 05 '19 at 23:05
  • @JFord - Fair enough. I rushed the example, but it still does complete the observable before the subscription completes. – Enigmativity Dec 05 '19 at 23:47

2 Answers2

1

I think the Disposable interface on the Observable paradigm is used solely for the purpose of getting rid of the subscription (i.e, stopping the callback on the observed events), as Theodor Zoulias pointed out. It doesn't manage any resources on the stream whatsoever. You might be confusing the use of the Disposable interface on other scenarios.

As regards to disposing subscriptions:

One of the use cases I can see for returning a Disposable is when when you have more than one to call the function on: supposing you had a list of Observables, you could iterate on it and call the function .Dispose() to cancel multiple subscriptions at once.

You could also pass that stream as a disposable to another Observable, to be disposed when some event occurs. Since the entire RX paradigm is about not knowing when things will be executed, this is interesting. I worked at an application where I had to cancel a subscription if a certain event happened, and I passed the Observable Subscription (IDisposable) to the Observer of such event/stream.

Something on these lines:

 IDisposable subscription1 = observableOne.Subscribe(_ => # code omitted); 
 observableTwo.Subscribe(_ => { 
    subscription1?.Dispose(); 
    subscription1 = null; 
});

As Enigmaticy has pointed out, although this exemplifies my point, a better way to accomplish this would be:

observableOne.TakeUntil(observableTwo).Subscribe(_ => #code ommited);

I haven't worked with C# in a while but these are the use cases I can see on using vs Disposable as object. It gives you greater flexibility on when you want to cancel your subscriptons :~

cesartalves
  • 1,507
  • 9
  • 18
  • Hmm I'm not sure if this fully answers my question. But it sounds like the idea is to hand off disposal of resources to the observable itself. Could it just be a performance overhead reason? Should we just avoid calling `dispose` in the `subscribe` implementation and rather, either return a `disposable` or an `action` that will dispose any resources? – JFord Nov 29 '19 at 21:45
  • I think the Disposable interface on the Observable paradigm is used solely for the purpose of getting rid of the subscription (i.e, stopping the callback on the observed events), as Theodor Zoulias pointed out. It doesn't manage any resources on the stream whatsoever. The .Dispose() method will simply call OnComplete on the Observers. – cesartalves Nov 30 '19 at 17:24
  • 2
    "The .Dispose() method will simply call OnComplete on the Observers" - No, that doesn't happen. The observable just ceases to exist. `OnCompleted` is not called when you call `.Dispose()` on a subscription. – Enigmativity Dec 05 '19 at 11:54
  • And what does "pass that stream as a disposable to another Observable" mean? I don't understand how you would do that. – Enigmativity Dec 05 '19 at 11:55
  • And what does "I worked at an application where I had to cancel a subscription if a certain event happened, and I passed the Observable as a variable to that stream" mean? – Enigmativity Dec 05 '19 at 11:56
  • It's true, Dispose won't call OnComplete. I thought I'd seen that on the microsoft docs. I'll remove that part from my answer. However, the Observable DOES NOT cease to exist, only that specific subscription. As for the other question: `subscription1 = observableOne.Subscribe(_ => # code omitted);` `observableTwo.Subscribe(_ => { ` `subscription1?.Dispose(); ` `subscription1 = null;` `});` You could use something on those lines to cancel a subscription on a given Event/Reactive OnNext; – cesartalves Dec 05 '19 at 12:34
  • From my understanding . The subscription will only close automatically if `OnComplete` or `OnError` is called by the observer. Otherwise it is up to the subscriber to dispose of the subscription. I also believe that if the Observable is GC'd the subscription will be disposed so it's not always necessary to manually dispose if both the Observable & Subscription are isolated to the same life-cycle. – JFord Dec 05 '19 at 16:52
  • Exactly. The `IDisposable` returned by the subscription is there only in the case that, for some reason, you'd like to cancel it before the `OnComplete or OnError`. The `using` keyword is merely syntatic sugar for calling .Dispose() on a resource. So you don't need it the majority of times, you can just leave it for the GC. There's an example [here](https://learn.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh229114(v%3Dvs.103)) where the Dispose method is called inside a using block. – cesartalves Dec 05 '19 at 18:00
  • @cesartalves - Yes, the observable still exists, but the subscription disappears. I wasn't clear on that. – Enigmativity Dec 06 '19 at 06:15
  • @cesartalves - Please don't use the `subscription1?.Dispose();` method to finish another subscription based on an observable producing a value. Just use `observableOne.TakeUntil(observableTwo)`. If ever you are subscribing or unsubscribing within a subscription you are probably doing something wrong. – Enigmativity Dec 06 '19 at 06:16
  • 1
    @cesartalves - The GC never calls `.Dispose()` on any object. It **may** call a finalizer which does call `.Dispose()`, but you **have to check the implementation of every object to know if that is the case.** – Enigmativity Dec 06 '19 at 06:18
  • @cesartalves - I believe the OP is using `using` inside the `Observable.Create`. The question isn't about using a `using` outside of one. – Enigmativity Dec 06 '19 at 06:20
  • I was refering to the GC as a finalizer, not that it called .Dispose explicitly. Yes, using TakeUntil would be best thing to do on that scenario, it was merely an example. If the event was not an 'Observable', you wouldn't have access to TakeUntil and wouldn't have it accessible. However you work up some way to transform it into an Observable, so I agree with you, TakeUntil would be best on that scenario I showed as example. – cesartalves Dec 06 '19 at 12:17
  • Isn't using a syntax sugar for calling dispose, regardless of the context? In any way I believe that discussion digresses a bit from OP's intent looking by his chosen answer. – cesartalves Dec 06 '19 at 12:19
0

Thanks to everyone responding in this post it's helped me understand a bit better. I've had a lot of stumbling in my understanding of RX and I think a lot of this just comes down to limited documentation and seems like many people online don't quite understand perfectly either so there's a lot of misinformation to sort through.

This other answer does the trick for me https://stackoverflow.com/a/7707768/7183974 .

What it really comes down to is for when we have non-blocking code in our Observable.Create method. So when our observable is subscribed to we instantly return a disposable that can clean up any asynchronous / concurrent processes in the event that we need to cancel a subscription early.

This is necessary for cases where maybe your observable is using other async (push-based) code.

For iterative (pull-based) code that you simply want to be push based then you can use Observable.Create but TBH I think just using an Iterator is better and if you need it to be a push-based API then just use ToObservable.

I was trying to implement a push-based iterator so the disposable seemed redundant to me which is what confused me. I've since refactored my code to be pull-based and if I ever were to need it to be push-based again I would just use ToObservable.

JFord
  • 126
  • 1
  • 9