2

我创建了基于 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 个实例)并在它们被处理时正在被订阅块消耗。我想忽略并立即处理它们。

4

1 回答 1

0

我认为我能够重现此问题。下面的序列启动了一个每 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太复杂了,我无法弄清楚。我的猜测是 aSystem.Timers.Timer在内部用作刻度生成器,并且它的抑制刻度以某种方式累积。每个抑制的滴答声平均泄漏约 40 个字节。

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

于 2020-12-02T05:00:08.210 回答