C# Rx缓冲区内存泄漏

C# 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 =

我对Rx
缓冲区
操作符有一个奇怪的问题,我找不到合适的解决方案,我不知道我做错了什么。如果第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);