1

I'm using the Reactive .NET extensions and I wonder about its disposal. I know in some cases it's good to dispose it like that: .TakeUntil(Observable.Timer(TimeSpan.FromMinutes(x))). I

First case

In this case, I have a timer that triggers after x seconds and then it completes and should be disposed.

public void ScheduleOrderCancellationIfNotFilled(string pair, long orderId, int waitSecondsBeforeCancel)
{
    Observable.Timer(TimeSpan.FromSeconds(waitSecondsBeforeCancel))
        .Do(e =>
        {
            var result = _client.Spot.Order.GetOrder(pair, orderId);

            if (result.Success)
            {
                if (result.Data?.Status != OrderStatus.Filled)
                {
                    _client.Spot.Order.CancelOrder(pair, orderId);
                }
            }
        })
        .Subscribe();
}

Second case

In this case, the timer runs on the first second and then it repeats itself on each 29 minutes. This should live until its defining class is disposed. I believe this one should be disposed with IDisposable implementation. How?

var keepAliveListenKey = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromMinutes(29))
    .Do(async e =>
    {
        await KeepAliveListenKeyAsync().ConfigureAwait(false);
    })
    .Subscribe();

Edit

I also want it to be using a Subject<T> which makes it easier to dispose and to reset the subscription.

For ex. Reset and Dispose observable subscriber, Reactive Extensions (@Enigmativity)

public class UploadDicomSet : ImportBaseSet
{
    IDisposable subscription;
    Subject<IObservable<long>> subject = new Subject<IObservable<long>>();

    public UploadDicomSet()
    {
        subscription = subject.Switch().Subscribe(s => CheckUploadSetList(s));
        subject.OnNext(Observable.Interval(TimeSpan.FromMinutes(2)));
    }

    void CheckUploadSetList(long interval)
    {
        subject.OnNext(Observable.Never<long>());
        // Do other things
    }

    public void AddDicomFile(SharedLib.DicomFile dicomFile)
    {
        subject.OnNext(Observable.Interval(TimeSpan.FromMinutes(2)));
        // Reset the subscription to go off in 2 minutes from now
        // Do other things
    }
}
nop
  • 4,711
  • 6
  • 32
  • 93
  • im not using that library - but any class that implements `IDisposable` should be disposed at some point, so keep a reference and dispose it. Objects that don't implement that interface shouldn't be disposed - clearly. What are you asking precisely? – sommmen Feb 01 '21 at 08:09
  • It implements it, but what is the correct way to do it in `Dispose()`, especially the 2nd case – nop Feb 01 '21 at 08:11
  • `If (myReferencedObject != null) myReferencedObject.Dispose();` ? Just call dispose once you're done with it, or call it when closing the application down. – sommmen Feb 01 '21 at 08:13
  • As a side note, the `Do` operator does not support asynchronous delegates. The lambda passed is [async void](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void). Also be aware that when you `Subscribe` to an observable without providing an `onError` handler, any exception that may happen will be unhandled and will have the same effect as an unhandled exception in a traditional event handler (it will crash the process). – Theodor Zoulias Feb 01 '21 at 09:42

2 Answers2

3

In the first case it gonna be disposed automatically. It is, actually, a common way to achieve automatic subscription management and that's definitely nice and elegant way to deal with rx.

In the second case you have over-engineered. Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)) is itself sufficient to generate a sequence of ascending longs over time. Since this stream is endless by its nature, you right - explicit subscription management is required. So it is enough to have:

var sub = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)).Subscribe()

...and sub.Dispose() it later.

P.S. Note that in your code you .Do async/await. Most probably that is not what you want. You want SelectMany to ensure that async operation is properly awaited and exceptions handled.


Answering your questions in the comments section:

What about disposing using Subject instead?

Well, nothing so special about it. Both IObserver<>, IObservable<> is implemented by this class such that it resembles classical .NET events (list of callbacks to be called upon some event). It does not differ in any sense with respect to your question and use-case.

May you give an example about the .Do with exception handling?

Sure. The idea is that you want translate your async/await encapsulated into some Task<T> to IObservable<T> such that is preserves both cancellation and error signals. For that .SelectMany method must be used (like SelectMany from LINQ, the same idea). So just change your .Do to .SelectMany.

Observable
    .Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
    .SelectMany(_ => Observable.FromAsync(() => /* that's the point where your Task<> becomes Observable */ myTask))

I'm confused again. Do I need IObservable<IObservable> (Select) or IObservable (SelectMany)

Most probably, you don't need switch. Why? Because it was created mainly to avoid IO race conditions, such that whenever new event is emitted, the current one (which might be in progress due to natural parallelism or asynchronous workflow) is guaranteed to be cancelled (i.e. unsubscribed). Otherwise race conditions can (and will) damage your state.

SelectMany, on the contrary, will make sure all of them are happen sequentially, in some total order they have indeed arrived. Nothing will be cancelled. You will finish (await, if you wish) current callback and then trigger the next one. Of course, such behavior can be altered by means of appropriate IScheduler, but that is another story.

Zazaeil
  • 3,900
  • 2
  • 14
  • 31
  • What about disposing using `Subject` instead? – nop Feb 01 '21 at 11:12
  • May you give an example about the async SelectMany instead of `.Do`, with exception handling? – nop Feb 01 '21 at 11:13
  • I still don't get it how to translate it to SelectMany. It's not so straightforward – nop Feb 01 '21 at 11:25
  • @nop check updates, didn't check if it compiles, but should be sufficient to get an idea – Zazaeil Feb 01 '21 at 11:27
  • Thank you! One last question and I will accept your answer. https://pastebin.com/1kTfYGSz, is everything okay with what I did based on your answer? – nop Feb 01 '21 at 12:02
  • @nop, overengineered again. You need `IObserble>`, i.e. stream of streams? Then `Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)) .Select(_ => Observable.FromAsync(async () => await Task.Delay(100))).Switch()` is sufficient. In other words, `Subject` aint needed. However, I see not reason why `IObs>` is needed in your case. Normally, you would merge it with help of `SelectMany` as I showed. – Zazaeil Feb 01 '21 at 12:05
  • I need the `.Switch()` so I can reset the subscription at some point. – nop Feb 01 '21 at 12:22
  • Then you may rely upon shorter version I've shown. – Zazaeil Feb 01 '21 at 12:22
  • I'm confused again. Do I need `IObservable>` (Select) or `IObservable` (SelectMany) – nop Feb 01 '21 at 12:29
  • That .Switch() is because the Subject won't push the next value in case of reseting. https://stackoverflow.com/questions/45308073/reset-and-dispose-observable-subscriber-reactive-extensions – nop Feb 01 '21 at 12:35
  • @nop you misunderstand what `Switch` does and why. Check my updates. – Zazaeil Feb 01 '21 at 12:37
  • But if I do that https://i.imgur.com/zOib4AD.png, it won't even start the timer. – nop Feb 01 '21 at 12:44
  • @nop 'cause you forgot to Subscribe. Like LINQ, Rx is lazy by default. Nothing ever happens until you force it to. – Zazaeil Feb 01 '21 at 12:45
  • I'm subscribing to the `_subject` and then changing its state with OnNext. I believe – nop Feb 01 '21 at 12:47
  • I tested that but can u do it with Subject. I really need that Subject. – nop Feb 01 '21 at 13:04
  • 1
    Then update your question and explain for your `Subject` stands for. I seems for me that you are totally confused. – Zazaeil Feb 01 '21 at 13:05
  • 1
    @nop - Please don't ask supplementary questions in the comments. It makes the answer so difficult to understand. If you need to adjust your question, do so by appending to it. Then everyone can clearly understand what is being asked without needing to read so many comments. – Enigmativity Feb 03 '21 at 02:32
  • @Enigmativity, added the Subject requirement to the question – nop Feb 03 '21 at 06:23
0

Reactive Observable Subscription Disposal (@Enigmativity)

The disposable returned by the Subscribe extension methods is returned solely to allow you to manually unsubscribe from the observable before the observable naturally ends.

If the observable completes - with either OnCompleted or OnError - then the subscription is already disposed for you.

One important thing to note: the garbage collector never calls .Dispose() on observable subscriptions, so you must dispose of your subscriptions if they have not (or may not have) naturally ended before your subscription goes out of scope.

First case

Looks like I don't need to manually .Dispose() the subscription in the first case scenario because it ends naturally.

Dispose is being triggered at the end.

var xs = Observable.Create<long>(o =>
{
    var d = Observable.Timer(TimeSpan.FromSeconds(5))
        .Do(e =>
        {
            Console.WriteLine("5 seconds elapsed.");
        })
        .Subscribe(o);

    return Disposable.Create(() =>
    {
        Console.WriteLine("Disposed!");
        d.Dispose();
    });
});

var subscription = xs.Subscribe(x => Console.WriteLine(x));

Second case

but in the second case, where it doesn't end "naturally", I should dispose it.

Dispose is not triggered unless manually disposed.

var xs = Observable.Create<long>(o =>
{
    var d = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
        .Do(e =>
        {
            Console.WriteLine("Test.");
        })
        .Subscribe(o);

    return Disposable.Create(() =>
    {
        Console.WriteLine("Disposed!");
        d.Dispose();
    });
});

var subscription = xs.Subscribe(x => Console.WriteLine(x));

Conclusion

He gave such a nice examples, it's worth seeing if you are asking yourself the same question.

nop
  • 4,711
  • 6
  • 32
  • 93