Использование операции Rx Window с TimeSpan над наблюдаемым событием предотвращает сборку мусора - PullRequest
2 голосов
/ 13 февраля 2012

Я пытаюсь убедиться, что экземпляры класса, использующие Rx, не пропускают. Я сократил проблему до самого необходимого, и похоже, что функция Window является ключом к решению проблемы.

Примите во внимание следующее:

<!-- 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!");     
    }
}

Когда я применяю функцию Window с селектором открытия TimeSpan и подписываюсь на него, (1) foo никогда не будет GC'd.

Если я не подпишусь (2) или использую другой открывающий селектор (3), тогда это так.

Кроме того, если я использую наблюдаемую холодность в качестве источника, то foo независимо от GC.

Почему функция Window со специальным TimeSpan, и как я могу убедиться, что foo будет GC'd при его использовании?

1 Ответ

2 голосов
/ 13 февраля 2012

Это выглядит правильно для меня.

Вам не хватает выделения подписки для поля IDisposable. Вы не распоряжаетесь подпиской и не вызываете GC.WaitForPendingFinalizers ();

Вы можете исправить тест следующим:

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!");     
    }
}

Тест теперь может стать

//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();

Надеюсь, это поможет. Также ознакомьтесь с постом Lifetime Management в моем вступлении к серии блогов Rx.

ОБНОВЛЕНИЕ : Моя онлайн-книга на IntroToRx.com заменяет серию блогов. Наиболее уместным здесь представляется глава Lifetime management .

...