Keeping references to `IDisposable` when using the Reactive Extensions for .NET: always, never, or sometimes?
Up until now I have zealously kept every reference to 开发者_开发知识库the IDisposable
returned from any .Subscribe(...)
, .Connect(...)
, etc, method within Rx. I've done this because of my fear that a garbage collections will dispose the disposable if I don't keep the reference.
However, I did a test in LINQPad where I did some calls GC.Collect()
on a .Subscribe(...)
where I didn't keep the reference and guess what? The world didn't end and the subscription ran to completion.
In further testing I found that my subscription was disposed of immediately after .OnComplete()
without my intervention.
This leads me to understand that, at least for .Subscribe(...)
, that the only reason to keep a reference to the subscription is to force the subscription to end before its normal completion. It's more like a cancellation token.
So are all Rx disposables used for cancelling rather than keeping alive?
What, then, are the rules for hanging on to an IDisposable
?
No need to hold on to the IDisposable objects unless you want to unsubscribe from an observable source in the future. Similar for the Schedule methods on IScheduler, whose returned IDisposable objects can be used to cancel the scheduled action.
The garbage collector doesn't care about IDisposable object directly, nor do we implement finalizers on any of our objects, so basically lifetime management of subscriptions etc. is entirely up to you in the world of Rx. Compare it to Win32 handles if you want, with Dispose being the moral equivalent to CloseHandle.
Trivia: at some point during the design of Rx, the cancelable operations returned an Action whose invocation would cause the cancellation. Quite functionally inspired in nature, but less obvious to some. So, we decided to go with an interface that already represents a notion of resource management, and IDisposable was the obvious choice. This said, it departs a little from typical usage of the interface elsewhere in the .NET Framework:
- You'll often just drop the IDisposable object on the floor, in particular for infinite sequences you never unsubscribe from.
- In most cases, you won't use the using statement for Rx IDisposable objects, due to the inherent asynchronous nature of the framework.
Hope this helps,
-Bart (Rx team)
The IDisposable
returned by IScheduler.Schedule()
is only useful if you want to cancel the scheduled action (that is, before it's happened)
The IDisposable
returned by IObservable.Subscribe
and IConnectableObservable.Connect
are equivalent, in that disposing either will terminate the subscription to the source observable.
As for garbage collection, while Rx makes it a little more difficult to gauge, it's still bound by the rules of the GC. If the source of your observable is rooted (like the event of a UI control), then you don't need to worry about it being GC'd.
If your source is a Observable.Interval
or something else is basically a recursive IScheduler
call, then the scheduler should keep it alive (ie. the thread pool, task pool, or dispatcher) since the observable is rooted to the scheduler (via IScheduler.Schedule
).
Well, firstly I think its important to point out the garbage collection and disposal of objects / the IDisposable
interface are completely separate - in particular the garbage collector will never directly dispose of an object by calling the Dispose
method of an IDisposable
(unless the finalizer for that object does this itself).
As for when you should hang on to an IDisposable
, you should maintain a reference to any IDisposable
object that you need to dispose - it sounds like I'm stating the obvious and thats because I am! :-). Unless the disposable objects lifetime is longer than a single method the using
keyword is normally used:
using (var myDisposableObject = GetSomeDisposableObject())
{
myDisposableObject.DoThings();
}
This limits the scope of myDisposableObject
(which helps avoid attempting to use an object that has been disposed) and ensures that the object is correctly disposed of even if an exception is thrown.
It may be that for a certain class / API you don't need to (or shouldn't) dispose of an object that is returned to you, however that is entirely up to the API / class that returned that disposable object.
Yes, The IDisposable
, which is returned from Subscribe()
is only needed to Unsubscribe(). Of course, the Unsubscribe() call doesn't exist, you would Dispose()
instead, if needed.
精彩评论