Reputation: 29776
I am trying to make sure instances of a class using Rx
don't leak. I have reduced the problem down to the bare essentials and it looks like the Window function is key to the problem.
Consider the following:
<!-- language: c# -->
using System;
using System.Reactive.Linq;
class Program
{
static void Main(string[] args)
{
var foo = new Foo();
Console.WriteLine("Press any key...");
Console.ReadKey(true);
foo = null;
GC.Collect();
Console.WriteLine("Press any key...");
Console.ReadKey(true);
}
}
public class Foo
{
private event EventHandler MyEvent;
public Foo()
{
var subs = Observable.FromEventPattern(
e => MyEvent += e, e => MyEvent -= e);
// (1) foo is never GC'd
subs.Window(TimeSpan.FromSeconds(1)).Subscribe();
// (2) foo is GC'd
//subs.Window(TimeSpan.FromSeconds(1));
// (3) foo is GC'd
// subs.Window(1);
}
~Foo()
{
Console.WriteLine("Bye!");
}
}
When I apply the Window function with a TimeSpan opening selector and subscribe to it, (1) foo is never GC'd.
If I don't subscribe (2), or I use a different opening selector (3), then it is.
Also, if I use a cold observable as the source then foo is GC'd regardless.
Why is the Window function with a TimeSpan special, and how do I make sure foo will be GC'd when using it?
Upvotes: 2
Views: 414
Reputation: 10783
That looks correct to me.
You are missing the allocation of the subscription to an IDisposable field. You dont dispose of the subscription, and you dont call GC.WaitForPendingFinalizers();
You can fix the test up with the following:
public class Foo : IDisposable
{
private event EventHandler MyEvent;
private readonly IDisposable _subscription;
public Foo()
{
var subs = Observable.FromEventPattern(
e => MyEvent += e,
e => MyEvent -= e);
// (1) foo is never GC'd
//subs.Window(TimeSpan.FromSeconds(1)).Subscribe();
_subscription = subs.Window(TimeSpan.FromSeconds(1)).Subscribe();
// (2) foo is GC'd
//subs.Window(TimeSpan.FromSeconds(1));
// (3) foo is GC'd
// subs.Window(1);
}
public void Dispose()
{
_subscription.Dispose();
}
//TODO: Implement Dispose pattern properly
~Foo()
{
_subscription.Dispose();
Console.WriteLine("Bye!");
}
}
The test can now become
//foo = null; //This will just change our reference, the object sill lives and has stuff happening with timers etc..
foo.Dispose(); //Dispose instead of killing reference
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
I hope that helps. Also check out the Lifetime Management post on my intro to Rx blog series.
UPDATE: My online book at IntroToRx.com replaces the blog series. Most relevant here seems to be the Lifetime management chapter.
Upvotes: 2