C#-SelectMany使用ReactiveExtensions占用大量内存
作者:互联网
我想创建一个接收图像并返回一些派生对象的管道.
我正在使用一系列位图,对于每个位图我都执行任务(即异步).如此简单.但是,我发现内存消耗确实很高.为了说明问题,我创建了可以运行的测试.
请查看一下内存,因为它将占用多达400 MB的RAM.
我该怎么做才能避免占用太多内存?这里发生了什么事?
[Fact]
public async Task BitmapPipelineTest()
{
var bitmaps = Enumerable.Range(0, 100).Select(_ => new WriteableBitmap(800, 600, 96, 96, PixelFormats.Bgr24, new BitmapPalette(new List<Color>() { new Color() })));
var bitmapsObs = bitmaps.ToObservable();
var processed = bitmapsObs.SelectMany(bitmap => DoSomethingAsync(bitmap));
processed.Subscribe();
await Task.Delay(20000);
}
private async Task<object> DoSomethingAsync(BitmapSource bitmap)
{
await Task.Delay(1000);
return new object();
}
解决方法:
因此,我认为该问题不一定是由于SelectMany甚至是响应式扩展引起的.看起来WriteableBitmap使用的是非托管内存:source code.我认为问题是,您正在非常连续地创建一堆相对较小的托管对象,这些对象占用了大量的非托管内存.从MSDN:
If a small managed object allocates a large amount of unmanaged memory, the runtime takes into account only the managed memory, and thus underestimates the urgency of scheduling garbage collection.
但是我们可以通过使用GC.AddMemoryPressure和GC.RemoveMemoryPressure函数来给垃圾收集器提示.这将有助于GC改进其计划.在进行此操作之前,我们需要对分配的非托管内存量有所了解.我相信非托管内存用于存储像素阵列,因此我认为一个很好的估计是像素宽度乘以像素高度乘以每个通道中的位数乘以通道数.从MSDN开始,每个通道和4个通道看起来有32位(4个字节).
我使用类似于以下代码的代码运行了一些测试,并获得了很好的结果:
var processed =
Enumerable
.Range(0, 100)
.Select(_ => new WriteableBitmap(
800,
600,
96,
96,
PixelFormats.Bgr24,
new BitmapPalette(new List<Color>() { new Color() })))
.Select(x => new { Bitmap = x, ByteSize = x.PixelWidth * x.PixelHeight * 4 * 4)
.ToObservable()
.Do(x => GC.AddMemoryPressure(x.ByteSize))
.SelectMany(x => DoSomethingAsync(x.Bitmap));
processed
.Subscribe(x => GC.RemoveMemoryPressure(x.ByteSize));
但是,如果您的源发布位图的速度比处理位图的速度快,那么您仍然会遇到问题.背压将导致内存分配的速度快于释放内存的速度.
不过,老实说,您是否真的将位图推送给您?我不知道您的实际程序是什么样的,但是在您的示例代码中,这显然是基于拉的系统.如果是基于拉动的系统,您是否考虑过PLINQ? PLINQ非常适合这类事情.它使您可以很好地控制并发性,而不必担心背压.
标签:system-reactive,reactive-programming,c,net 来源: https://codeday.me/bug/20191118/2030144.html