【问题标题】:Rx - Where method causes memory leak?Rx - 方法在哪里导致内存泄漏?
【发布时间】:2018-08-12 00:19:29
【问题描述】:

我创建了基于 Rx 间隔的简单触发器。它会在每个特定间隔生成信号,并通过 Where 方法过滤,如下所示(简化示例):

var isActive = false;

Observable.Interval(TimeSpan.FromMilliseconds(1))
            .Where(_ => !isActive)
            .Subscribe(_ =>
            {
                isActive = true;
                Console.WriteLine("New subscription item");
                Thread.Sleep(30 * 1000); // simulate time-expensive work
                Console.WriteLine("Finished");
                isActive = false;
            });

当 isActive 标志设置为 true 时,我想忽略元素。它似乎有效,因为每 30 秒调用一次订阅块(模拟耗时的工作),但是当我分析这样简单的应用程序时,我看到许多 Action 对象存储在内存中(每秒约 60-70 个实例)并在它们被处理时正在被订阅块消耗。我想忽略并立即处理它们。

【问题讨论】:

  • 请记住,Windows 无法每 1 毫秒运行一次计时器。它可以做的最好的事情是大约 16 毫秒(从内存中)。现在 1000 毫秒 / 16 毫秒 = 62.5。这可能解释了您获得的 60 到 70 个实例。
  • 尝试在.Interval 之后拨打.Synchronize() 电话,看看问题是否会消失。
  • 你确定有内存泄漏吗?当然,这些可观察对象将很快连续创建,导致大量开销,但当过滤器丢弃它们时,它们应该被垃圾回收。

标签: c# memory-leaks where system.reactive


【解决方案1】:

我认为我能够重现此问题。下面的序列启动了一个每 10 毫秒计时一次的计时器。滴答数每 3 秒采样一次,然后与当前内存使用情况一起打印在控制台中。测试总时长为 5 分钟。

Observable
    .Interval(TimeSpan.FromMilliseconds(10))
    //.Do(x => Thread.Sleep(1000)) // Uncomment this line to see memory leak
    .Sample(TimeSpan.FromSeconds(3))
    .Select((x, i) => (Sample: i + 1, Ticks: x + 1))
    .Do(x => Console.WriteLine($"Sample #{x.Sample,-2}, Ticks: {x.Ticks,6:#,0}"
        + $", Memory usage: {GC.GetTotalMemory(true):#,0} bytes"))
    .IgnoreElements()
    .Timeout(TimeSpan.FromMinutes(5))
    .Materialize()
    .Wait();

这是输出:

Sample #1 , Ticks:    193, Memory usage: 160,048 bytes
Sample #2 , Ticks:    383, Memory usage: 160,440 bytes
Sample #3 , Ticks:    573, Memory usage: 161,000 bytes
...
Sample #49, Ticks:  9,345, Memory usage: 169,688 bytes
Sample #50, Ticks:  9,537, Memory usage: 169,688 bytes
Sample #51, Ticks:  9,730, Memory usage: 169,688 bytes
...
Sample #97, Ticks: 18,509, Memory usage: 176,120 bytes
Sample #98, Ticks: 18,700, Memory usage: 176,120 bytes
Sample #99, Ticks: 18,892, Memory usage: 176,120 bytes

程序使用的内存在整个测试期间都是稳定的。

现在让我们取消注释 .Do(x => Thread.Sleep(1000)) 行,它会阻止每个滴答的产生 1 秒。这是新的输出:

Sample #1 , Ticks:      2, Memory usage: 166,432 bytes
Sample #2 , Ticks:      5, Memory usage: 173,136 bytes
Sample #3 , Ticks:      8, Memory usage: 185,424 bytes
...
Sample #32, Ticks:     95, Memory usage: 367,712 bytes
Sample #33, Ticks:     98, Memory usage: 367,712 bytes
Sample #34, Ticks:    101, Memory usage: 367,712 bytes
...
Sample #65, Ticks:    194, Memory usage: 563,576 bytes
Sample #66, Ticks:    197, Memory usage: 563,576 bytes
Sample #67, Ticks:    200, Memory usage: 563,576 bytes
...
Sample #97, Ticks:    290, Memory usage: 956,792 bytes
Sample #98, Ticks:    293, Memory usage: 956,792 bytes
Sample #99, Ticks:    296, Memory usage: 956,792 bytes

使用的内存几乎呈线性增长,每分钟大约增加 150 KB。

这里肯定发生了一些事情,但是Timer 运算符的source code 太复杂了,我无法弄清楚。我的猜测是 System.Timers.Timer 在内部用作刻度生成器,并且它的抑制刻度以某种方式累积。每个抑制的滴答声平均泄漏约 40 个字节。

Windows 10、C# 8、.NET Core 3.1.3、System.Reactive 4.4.1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多