7

What is the recommended way to dispose of subscriptions that are created in a loop? In the following contrived example I'm generating subscriptions in a for loop and adding them to a List and disposing them explicity by for eaching over the List This seems a bit smelly to me and I'm thinking that there has to be a cleaner way to clean up the subscriptions unless the GC disposes them when it runs. Do I need to explicity Dispose the subscriptions?

class Program
{
    static void Main(string[] args)
    {
        Func<int, IEnumerable<int>> func = x =>
        {
            return Enumerable.Range(0, x);
        };

        List<IDisposable> subsriptions = new List<IDisposable>();
        for (int i = 1; i < 10; i++)
        {
            var observable = func(i).ToObservable().ToList();
            var subscription = observable.Subscribe(x => Console.WriteLine(x.Select(y => y.ToString()).Aggregate((s1, s2) => s1 + "," + s2)));
            subsriptions.Add(subscription);
        }
        Console.ReadLine();
        subsriptions.ForEach(s => s.Dispose());
    }
}
Askolein
  • 3,250
  • 3
  • 28
  • 40
Abhijeet Patel
  • 6,562
  • 8
  • 50
  • 93

1 Answers1

23

Subscriptions do not need to be disposed if the source completes. In your example, you would only dispose the subscription if you wanted to stop enumerating the Range before it was finished.

Even in that situation, it's more common to dispose of subscriptions by using an operator that terminates the subscription as part of its design like Take, TakeWhile, TakeUntil.

If you do want to combine several IDisposable subscriptions, CompositeDisposable is designed to do exactly that:

CompositeDisposable subsriptions = new CompositeDisposable();

subscriptions.Add(Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe());
subscriptions.Add(Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe());
subscriptions.Add(Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe());

subscriptions.Dispose();
Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • @Richard: can you elaborate on "it's more common to dispose of subscriptions by using an operator that terminates the subscription as part of its design". Are you saying that an operator that is predicate based like Where/TakeWhile etc. would implicity dispose the subscription if the condition for "taking elements" evaluates to false? Also, If the operator is a 'Select' instead of a predicate based filter would you need to explicity dispose of the subscription in that case? – Abhijeet Patel May 19 '11 at 18:13
  • Your understanding is correct on both counts, though if you added a `Take*` to that `Select`, the take would terminate the `Select`, which would terminate the source. – Richard Szalay May 19 '11 at 19:34
  • Excellent. Also one (unrelated) question.In most Rx examples I've seen based on console applications, you typically need a console.readline to keep the process alive till the observable sequence has been completely consumed(same thing for Task in TPL). Is there any mechanism in the API to register a WaitHandle that the caller can await on instead? – Abhijeet Patel May 20 '11 at 01:58
  • You can use `AutoResetEvent` but, honestly, that would be fighting the asynchrony. You're better of returning an `IObservable` (or `Task` via `ToTask()` it that still exists) all down the chain until you subscribe to it. – Richard Szalay May 20 '11 at 06:34
  • Agreed, but if you were creating multiple subscriptions say in a for loop how would you know that the processing of all the subcriptions have completed? I would imagine that you would need to set an event in the completed or error delegates to indicate that the subscription ran to completion wouldn't you? Also, the Rx hands on labs indicate a Run() operator on IObservable which would block the caller until the sequence has been processed by a Subscribe call, can't seem to find that in the API anywhere. – Abhijeet Patel May 21 '11 at 02:17
  • The `Run` operator may have been removed since then. If your intent is to run multiple sequences and then be informed of their completion you can use `ForkJoin`. Alternatively, you might want to look into `Prune`. – Richard Szalay May 21 '11 at 08:36