Search code examples
c#.netgarbage-collection

Does a variable lifetime end inside an Action Queue when calling method gets garbage collected?


I need an advice regarding a variable lifetime inside an Action Queue in a multi-threaded ASP.NET Web API environment.

One of my API receive requests that update the database and to send out emails. However, sending out emails would take too long to process and I have decided to surround that portion inside a producer/consumer queue using the example from below.

using System;
using System.Threading;
using System.Collections.Generic;

public class PCQueue : IDisposable
{
  private static readonly PCQueue instance = new TaskQueue(50);

  readonly object _locker = new object();
  Thread[] _workers;
  Queue<Action> _itemQ = new Queue<Action>();

  public PCQueue (int workerCount)
  {
    _workers = new Thread [workerCount];

    // Create and start a separate thread for each worker
    for (int i = 0; i < workerCount; i++)
      (_workers [i] = new Thread (Consume)).Start();
  }

  public static PCQueue Instance
    {
        get
        {
            return instance;
        }
    }

  public void Dispose()
  {
    // Enqueue one null item per worker to make each exit.
    foreach (Thread worker in _workers) EnqueueItem (null);
  }

  public void EnqueueItem (Action item)
  {
    lock (_locker)
    {
      _itemQ.Enqueue (item);           // We must pulse because we're
      Monitor.Pulse (_locker);         // changing a blocking condition.
    }
  }

  void Consume()
  {
    while (true)                        // Keep consuming until
    {                                   // told otherwise.
      Action item;
      lock (_locker)
      {
        while (_itemQ.Count == 0) Monitor.Wait (_locker);
        item = _itemQ.Dequeue();
      }
      if (item == null) return;         // This signals our exit.
      item();                           // Execute item.
    }
  }
}

But the code below is where I am not sure if the garbage collector would clean up the values of email_address when UpdateTask finishes.

    [HttpPost]
    public async Task<IHttpActionResult> UpdateTask()
    {
        List<Email> email_address = new List<Email>(); // Assuming there are a few records here
        // Update something here
        PCQueue.Instance.EnqueueItem(() =>
        {
            SendEmail(email_address);
        }

        return Ok();
    }

Solution

  • Since List is a reference type it will get garbage collected once nothing holds a reference to it. You pass the list to the SendEmail method, it will live at least till the SendEmail ends (assuming that you don't pass the list anywhere else or store it in a field/property somewhere).

    Some more general pointers:

    However, beware of creating race conditions by accessing the list in more than one thread. Also you might want to consider using ConcurrentQueue instead of Queue.

    Finally, if this is part of the ASP.NET app code I would make damn sure to either finish the queue before responding to the request or have a service to which I offload the long running queue (job server concept). I am not versed in risks regarding having threads run past the request lifetime, but I would imagine ASP can mess with it, leading to loss of enqueued work without it being complete.