James World
James World

Reputation: 29776

Use of the Rx Window operation with a TimeSpan over an observable event prevents garbage collection

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

Answers (1)

Lee Campbell
Lee Campbell

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

Related Questions