ConcurrentQueue 保存对象的引用或值?“内存不足”异常

adl*_*adl 5 c# memory queue concurrency

入队到 ConcurrentQueue 的对象是复制到队列中还是只是它们的引用?

我不明白任何场景。

解释:

我像这样定义了一个 ConcurrentQueue:

// BufferElement is a class I created
private ConcurrentQueue<BufferElement> _bufferQueue;
Run Code Online (Sandbox Code Playgroud)

我有一个被多次调用的函数,它的目的是将一个元素加入队列:

private void EnqueueElementToBuffer(string data, int moreData)
{
    // the bufferElement constructor is setting data and moreData to it's fields.
    BufferElement bufferElement = new BufferElement(data, moreData);
    bufferQueue.Enqueue(bufferElement);
}
Run Code Online (Sandbox Code Playgroud)

当我运行它时,我会在一段时间后出现内存不足异常。我想可能是因为垃圾收集器没有收集 ,bufferElement因为它仍然在 中被引用bufferQueue,所以我将函数更改为:

private void EnqueueElementToBuffer(string data, int moreData)
{
    // _bufferElement is now a filed of the class
    _bufferElement.Data = data;
    _bufferElement.MoreData = moreData;
    bufferQueue.Enqueue(_bufferElement);
}
Run Code Online (Sandbox Code Playgroud)

而且我没有得到异常,也不会根据 Windows 任务管理器中的内存来判断。

现在我认为问题已经解决了,因为当我将对象排入队列时,只有对对象的引用被复制到队列中,但我担心队列中的所有元素都在引用同一个对象,所以我检查了另一个我在另一个线程中的功能是:

    // while bufferQueue is not empty do the following
    BufferElement bufferElement = null;
    bufferQueue.TryDequeue(out bufferElement);
Run Code Online (Sandbox Code Playgroud)

我检查了几个元素的内容,发现它们的内容不同!所以如果排队到队列中的对象是按值复制的,为什么我一开始会出现内存不足异常?

Jar*_*Par 5

当您调用时,Enqueue仅引用的副本存储在ConcurrentQueue<T>. 但是这个引用是强保留的,这意味着它确实将实际引用的对象保留在内存中。在从引用中删除该元素之前,该元素将不符合收集条件ConcurrentQueue<T>

OutOfMemoryException当您切换到使用字段时没有看到 an 的原因是因为您从根本上改变了语义

  • 原始代码:将 N 个对 N 个元素的引用推送到队列中,因此它在内存中保存了 N 个元素
  • 更改代码:将 N 个对 1 个元素的引用推送到队列中,因此它在内存中保存了 1 个元素

这显着减少了ConcurrentQueue<T>对象图在内存中的内存量并防止了异常。

似乎这里的问题是您只是将元素加入队列的速度比处理它们的速度要快得多。只要这一点成立,您最终将耗尽应用程序中的内存。您的代码需要进行调整,使其不会达到此条件。

注意:这个答案是在假设BufferElement是 aclass而不是 a 的情况下编写的struct

注意 2:正如 Servy 在评论中指出的那样,您可能需要考虑切换到 a,BlockingCollection<T>因为它具有一些可能对您有所帮助的节流功能。

  • 您可以使用“BlockingCollection”(内部用作并发队列)并设置它的容量,这样当您尝试添加超过 X 个项目时,它会阻塞直到有可用空间,这可能是您减慢生产者所需的并使它们与消费者保持一致。 (3认同)