我认为我能够重现此问题。下面的序列启动了一个每 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