Search code examples
c#asp.net-coreconcurrencyqueueconcurrent-queue

How to release reference from the ConcurentQueue for the item dequeued from the concurrent queue with TryDequeue


I am using ConcurrentQueue (C#, ASP.NET Core) for holding tasks for the upload of the big files. I have a very big memory consumption even after items are dequeued from the concurrent queue. Items are not cleared from the memory.

I understand behavior of the ConcurrentQueue in ASP.NET Core which is in short: every group of the 32 items in a concurrent queue are saved in a separate segment. References are released for dequeued items only after all items from the particular segment are dequeued, and not after TryDequeue() method is called for a single item. This is a problem in my case because I can have very large amount in the memory if in the worst case 32 items are not cleaned, which are potentially very big zip files. I even don't expect to have more than 32 items in the queue in the same time.

Even I extract files and send them separately, every file/image inside can be also big by itself. The only thing which is good enough for me is to release reference for every dequeued item, without further postponing. Is it possible and how?

I tried to use StrongBox, recommended in some articles:

ConcurrentQueue<StrongBox<Func<CancellationToken, Task>>> 
workItems = new ConcurrentQueue<StrongBox<Func<CancellationToken, Task>>>();

// Enqueuing:
this.workItems.Enqueue(new StrongBox<Func<CancellationToken, Task>>(workItem));

// Dequeuing:
workItems.TryDequeue(out var workItem);
return workItem.Value;

This was not useful in my case. Memory was not freed. I don't know it my problem is related to the fact that I am sending reference to the function to the queue and it is somehow held in a memory. One of the parameters of this function is a concrete file for upload. Others are just strings.

To conclude, I would like to take care of the memory release for the item, after or during TryDequeue() method is called. Is it possible and how?


Solution

  • See the comments on the accepted answer here: Usage of ConcurrentQueue<StrongBox<T>>.

    The idea is that you can null the Value property of StrongBox. So your code becomes:

    // Dequeuing:
    workItems.TryDequeue(out var workItem);
    var returnValue = workItem.Value;
    workItem.Value = null; // see below
    return returnValue;
    

    By setting workItem.Value = null, you're removing the reference that the StrongBox holds to the item. So once your code is done using it there are no more references to the item and it can be collected. Sure, the queue continues to hold a reference to the StrongBox, but that thing is tiny.