在可观察事件上使用具有TimeSpan的Rx Window操作可以防止垃圾收集

本文关键字:Window 操作 Rx 事件 观察 TimeSpan | 更新日期: 2023-09-27 18:24:38

我正在努力确保使用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?

在可观察事件上使用具有TimeSpan的Rx Window操作可以防止垃圾收集

这对我来说是正确的。

您缺少对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上的在线书籍取代了博客系列。这里最相关的似乎是终身管理章节。