C# Rx缓冲区内存泄漏
我对RxC# Rx缓冲区内存泄漏,c#,memory-leaks,system.reactive,reactive-programming,C#,Memory Leaks,System.reactive,Reactive Programming,我对Rx缓冲区操作符有一个奇怪的问题,我找不到合适的解决方案,我不知道我做错了什么。如果第9行的缓冲区未使用EventLoopScheduler,则在没有从中推送任何项目(项目)时,它会在一段时间后开始泄漏内存 第1行的项是一个IObservable,它将从TCP套接字检索到的解析数据推送到下游。使缓冲区使用EventLoopScheduler可以解决问题,但会降低整体系统性能 如何解决此内存泄漏而不必强制缓冲区操作员使用EventLoopScheduler var groupedItems =
缓冲区操作符有一个奇怪的问题,我找不到合适的解决方案,我不知道我做错了什么。如果第9行的缓冲区
未使用EventLoopScheduler
,则在没有从中推送任何项目(项目
)时,它会在一段时间后开始泄漏内存
第1行的项是一个IObservable
,它将从TCP套接字检索到的解析数据推送到下游。使缓冲区
使用EventLoopScheduler
可以解决问题,但会降低整体系统性能
如何解决此内存泄漏而不必强制缓冲区
操作员使用EventLoopScheduler
var groupedItems = items
.GroupBy(entity => entity._type)
.Select(o => new {Type = o.Key, Categories = o.GroupBy(entity => entity._key)});
var ev = new EventLoopScheduler();
var collections = from item in groupedItems
from category in item.Categories
from entities in category.Buffer(intervalTime, intervalSize, /* ev */)
where entities.Any()
select new LogCollection(item.Type, category.Key, entities);
collections.Buffer(TimeSpan.FromSeconds(1)).Where(o => o.Any()).Subscribe(Insert);
更新:
经过一些调查后,Buffer
操作符似乎不是问题所在,它在EventLoopScheduler
上调度时“解决”了问题。在明显的绝望中,我发布了关键的代码片段,因为我是Rx的新手,我不知道我是否正确地使用了范例-因此,如果我误用了范例,请纠正我!:)
背景知识:应用程序通过TCP套接字检索二进制数据,并在转换后将其插入数据库
接收
客户端可以连接到服务器,从客户端发送的数据将被转换。如果约定中发生任何异常,它将捕获该异常并断开客户端的连接
public IObservable<LogEntity> StartListening(IDataConverter converter)
{
return Observable.Create<LogEntity>(observer =>
{
return _endPoint.ToListenerObservable(_backlog).Subscribe(client =>
{
var stream = client.ToClientObservable(_bufferSize, _waitHandle);
converter.Convert(stream)
.Catch<LogEntity, Exception>(exception =>
{
client.Close(); // dc client
return Observable.Empty<LogEntity>();
})
.Subscribe(observer.OnNext);
});
});
}
转换器
转换器使用Scan
操作符解析数据流。它内部可能发生异常。目前,异常将传播到StartListing
方法,在该方法中,发送坏数据的客户端将断开连接
public IObservable<LogMessage> Convert(IObservable<ArraySegment<byte>> bytes)
{
return bytes.Scan(
new
{
Leftovers = new byte[0],
Logs = new List<LogMessage>(),
},
(saved, current) =>
{
// Parse bytes
// Exception here if invalid data retrieved
return new
{
Leftovers = data.ToArray(),
Logs = logs,
};
})
.SelectMany(o => o.Logs);
}
公共IObservable转换(IObservable字节)
{
返回字节。扫描(
新的
{
剩余=新字节[0],
日志=新列表(),
},
(已保存,当前)=>
{
//解析字节
//如果检索到无效数据,则此处出现异常
还新
{
剩菜=data.ToArray(),
日志=日志,
};
})
.SelectMany(o=>o.Logs);
}
你们能看到任何可能导致内存泄漏的东西吗?这基本上是所有负责检索数据的代码,在数据发送到转换阶段之前对其进行转换(第一个问题)。此外,我用dotMemory工具确认了内存泄漏 示例代码中有几点值得注意。
首先,它不是@Enigmativity指出的MVCE,例如,项的类型是什么,它的值,它们的属性/(字段?),以及日志集合的类型
其次,您似乎正在运行过多的GroupBy
操作。这将创建可观察序列的3深度嵌套。我想你只需要GroupBy
一次,然后依靠另一个非输入来为你做正确的事情,即.GroupBy(entity=>new{entity.Type,entity.Key})
。我之所以这样说,是因为一旦你按两次分组,你似乎又把它全部拆开了
第三,缓冲两次。两个时间都用于检查空缓冲区。一次使用调度程序(可能),另一次不使用?第二个缓冲区似乎是多余的
第四,您似乎没有关闭任何GroupBy
“窗口”。这意味着,对于每个嵌套分组,您都在创建独立的缓冲区。根据您的平台,每一个都可以在线程/任务池上运行。因此,您可以将不受限制和未知级别的并发性释放到程序中。因此,当这些新组中的每一个都使用\u type
&\u key
的新组合创建时,您将创建新的缓冲区接收器,这些接收器将永远不会停止/处置/清理,并将持续消耗资源
第五,我们不知道你的记忆问题是否只是因为没有足够的记忆压力来强迫GC,所以你会看到记忆压力上升
我认为你的问题可以简化为:
from item in items
group item by new { item.Type, item.Key} into grp
from buffer in grp.Buffer(intervalTime, intervalSize, scheduler)
where buffer.Any()
select new LogCollection(grp.Key.Type, grp.Key.Key, buffer);
为了解决记忆压力的问题,我强烈建议你提供一些方法让一组人的记忆失效。即使这很简单,只要在一段时间后终止订阅,然后立即重新订阅(Retry
和Publish
在这里可能会有所帮助)。否则,如果您得到的类型/密钥对只出现一次,您将支付组的价格,从而在整个订阅的生命周期内支付该组的缓冲区
最后,在研究内存压力问题时,我建议实际捕获或分析您的应用程序,而不是浏览任务管理器,它会向您发送大量虚假信息。尝试GC.GetTotalMemory(true)
或一些WMI挂钩,甚至只跟踪GC.CollectionCount
值。示例代码中有一些值得注意的地方。
首先,它不是@Enigmativity指出的MVCE,例如,项的类型是什么,它的值,它们的属性/(字段?),以及日志集合的类型
其次,您似乎正在运行过多的GroupBy
操作。这将创建可观察序列的3深度嵌套。我想你只需要GroupBy
一次,然后依靠另一个非输入来为你做正确的事情,即.GroupBy(entity=>new{entity.Type,entity.Key})
。我之所以这样说,是因为一旦你按两次分组,你似乎又把它全部拆开了
第三,缓冲两次。两个时间都用于检查空缓冲区。一次使用调度程序(可能),另一次不使用?第二个缓冲区似乎是多余的
第四,您似乎没有关闭任何GroupBy
“窗口”。这意味着,对于每个嵌套分组,您都在创建独立的缓冲区。根据您的平台,每一个都可以在线程/任务池上运行。因此你是善良的
from item in items
group item by new { item.Type, item.Key} into grp
from buffer in grp.Buffer(intervalTime, intervalSize, scheduler)
where buffer.Any()
select new LogCollection(grp.Key.Type, grp.Key.Key, buffer);