我正在努力确保使用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!");
}
}
当我使用TimeSpan打开选择器应用Window函数并订阅它时,(1)foo永远不会是GC’d。
如果我没有订阅(2),或者我使用了不同的打开选择器(3),那么它就是。
此外,如果我使用一个冷的可观察对象作为源,那么foo就是GC’d。
为什么带有TimeSpan的Window函数是特殊的,以及我如何确保在使用它时foo会被GC?
这对我来说是正确的。
您缺少对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();
我希望这能有所帮助。还可以查看我的Rx博客系列介绍中的Lifetime Management帖子。
更新:我在IntroToRx.com上的在线书籍取代了博客系列。这里最相关的似乎是终身管理章节。