使用ConcurrentQueue时出现内存泄漏问题

9

使用 ConcurrentQueue 时出现了内存泄漏:

requestObject request = xxx;

Item obj= new Item ();
obj.MessageReceived += obj_MessageReceived;
obj.Exited += obj_Exited;

request.Key = obj.Key;

obj.AddRequest(request);

_queue.TryAdd(obj.Key, obj);

在“Exited”回调中,我处理了资源的释放:
void LiveSphere_Exited(string key)
{
    Item instance;

    _queue.TryRemove(key, out instance);

    Task.Factory.StartNew(() =>
    {
        var wait = new SpinWait();
        while (instance.MessageCount > 0)
        {
            wait.SpinOnce();
        }
    })
    .ContinueWith((t) =>
    {
         if (instance != null)
         {
             //Cleanup resources
             instance.MessageReceived -= obj_MessageReceived;
             instance.Exited -= obj_Exited;
             instance.Dispose();
             instance = null;
         }
    });
}

当我对代码进行分析时,我仍然有一个根据“Item”对象引用的对象,但我不知道在哪里可以释放它...

已经触发了退出方法并且队列中已经删除了“Item”对象。

当我阅读文档时,ConcurrentQueue会将引用复制到队列中。

您能帮我找出内存泄漏的原因吗?


5
不确定您的内存泄漏问题出在哪里。在.NET 4.0版本的ConcurrentQueue中存在内存泄漏问题,但在4.5版本中已经修复。您可以考虑研究BlockingCollection,它是一个更好的并发集合的封装。默认情况下,它内部使用ConcurrentQueue - Jim Mischel
我不敢轻易地称 .NET 4 的 ConcurrentQueue 是有泄漏的 - 主要是因为只有在你没有经常使用它时才会发生这种情况(直到弹出了几个项目后,它才会将数据设置为 null) - 那么,使用 ConcurrentQueue 还有什么意义呢? - Cory Nelson
谢谢Jim,但我已经在.NET 4.5中了,我需要FIFO队列,阻塞集合是FIFO吗? - dnx
@dnx:BlockingCollectionConcurrentQueue的包装器。所以,是的,它是FIFO。 - Jim Mischel
@gsscoder 使用阻塞集合将会表现更好(它只在需要同步时才进行同步,而不是总是同步,并且甚至可以避免许多你认为无法做到的情况),并且具有更多外部看起来原子的操作。 - Servy
显示剩余2条评论
2个回答

6
与标准的.NET队列不同,调用Dequeue()方法不会从集合中删除对该对象的引用。虽然这种行为已经从4.0版本更改为4.5版本(我已经阅读过,但尚未测试),但这不是一个错误,而是框架团队作为设计线程安全、可枚举集合的一部分而做出的有意识的设计决策。 本文提供了更多信息,包括使用StrongBox来包装进入ConcurrentQueue的对象的解决方法。在您可以转移到4.5框架之前,这应该是一个适当的解决方法。

4
考虑到其结果,我会称之为“有意的设计缺陷”。 - hypersw
2
我已经尝试过这种StrongBox方法,但只是延迟了问题。内存仍然增加到约1.7GIG,然后应用程序出现延迟问题并崩溃。 - user2520306
这是一篇由Stephen Toub撰写的相关文章的更新链接:https://devblogs.microsoft.com/pfxteam/concurrentqueuet-holding-on-to-a-few-dequeued-elements/。 - nwsmith

5

我已经研究了并发队列的实现。在Dequeue()被调用后,队列仍然会持有对象的引用。

并发队列使用段来存储数据。以下是段的TryRemove方法的一部分:

// If there is no other thread taking snapshot (GetEnumerator(), ToList(), etc), reset the deleted entry to null.
// It is ok if after this conditional check m_numSnapshotTakers becomes > 0, because new snapshots won't include 
// the deleted entry at m_array[lowLocal]. 
if (m_source.m_numSnapshotTakers <= 0)
{
    m_array[lowLocal] = default(T); //release the reference to the object. 
} 

因此,当您有一个不同的线程同时枚举队列时,您出队一个对象的引用将不会被设置为null。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接