Search code examples
c#monogarbage-collectionclrprofiler

IThreadPoolWorkItem not collected by GC


I have got an embedded debian board with mono running an .NET 4.0 application with a fixed number of threads (no actions, no tasks). Because of memory issues I used CLR-Profiler in Windows to analyse memory heap. Following diagram shows now, that IThreadPoolWorkItems are not (at least not in generation 0) collected:

Time line of heap memory

Now, I really dont have any idea where this objects are possibly used and why they arent collected.

  • Where could the issue be for this behaviour and where would the IThreadPoolWorkItem being used?
  • What can I do to find out where they are being used (I couldnt find them through searching the code or looking in CLR-Profiler yet).

Edit

...
private Dictionary<byte, Telegram> _incoming = new Dictionary<byte, Telegram>();
private Queue<byte> _serialDataQueue;     
private byte[] _receiveBuffer = new byte[2048];
private Dictionary<Telegram, Telegram> _resultQueue = new Dictionary<Telegram, Telegram>();
private static Telegram _currentTelegram; 
ManualResetEvent _manualReset = new ManualResetEvent(false);
... 

// Called from other thread (class) to send new telegrams
public  bool Send(Dictionary<byte, Telegram> telegrams, out IDictionary<Telegram, Telegram> received)
{
  try
  {
      _manualReset.Reset();
      _incoming.Clear(); // clear all prev sending telegrams
      _resultQueue.Clear(); // clear the receive queue

      using (token = new CancellationTokenSource())
      {
          foreach (KeyValuePair<byte, Telegram> pair in telegrams)
          {
              _incoming.Add(pair.Key, pair.Value);
          }

          int result = WaitHandle.WaitAny(new[] { token.Token.WaitHandle, _manualReset });

          received = _resultQueue.Clone<Telegram, Telegram>();
          _resultQueue.Clear();
          return result == 1;
      }
  }
  catch (Exception err)
  {
      ... 
      return false;
  }
 }


// Communication-Thread
public void Run()
{
  while(true)
  {
     ...

     GetNextTelegram();   // _currentTelegram is set there and _incoming Queue is dequeued

     byte[] telegramArray = GenerateTelegram(_currentTelegram,  ... ); 

     bool telegramReceived = SendReceiveTelegram(3000, telegramArray);
     ... 
   }
}

// Helper method to send and receive telegrams
private bool SendReceiveTelegram(int timeOut, byte[] telegram)
{
        // send telegram
        try
        {
            // check if serial port is open
            if (_serialPort != null && !_serialPort.IsOpen)
            {
                _serialPort.Open();
            }

            Thread.Sleep(10);
            _serialPort.Write(telegram, 0, telegram.Length);
        }
        catch (Exception err)
        {
            log.ErrorFormat(err.Message, err);
            return false;
        }

        // receive telegram
        int offset = 0, bytesRead;

        _serialPort.ReadTimeout = timeOut; 

        int bytesExpected = GetExpectedBytes(_currentTelegram);

        if (bytesExpected == -1)
            return false;

        try
        {
            while (bytesExpected > 0 &&
              (bytesRead = _serialPort.Read(_receiveBuffer, offset, bytesExpected)) > 0)
            {
                offset += bytesRead;
                bytesExpected -= bytesRead;
            }

            for (int index = 0; index < offset; index++)
                _serialDataQueue.Enqueue(_receiveBuffer[index]);

            List<byte> resultList;

            // looks if telegram is valid and removes bytes from _serialDataQueue
            bool isValid = IsValid(_serialDataQueue, out resultList, currentTelegram);

            if (isValid && resultList != null)
            {
                // only add to queue if its really needed!!
                byte[] receiveArray = resultList.ToArray();

                _resultQueue.Add((Telegram)currentTelegram.Clone(), respTelegram);
            }

            if (!isValid)
            {
                Clear();
            }   

            return isValid;
        }
        catch (TimeOutException err) // Timeout exception 
        {
            log.ErrorFormat(err.Message, err);
            Clear();
            return false;
        }  catch (Exception err)
        {
            log.ErrorFormat(err.Message, err);
            Clear();
            return false;
        }
    }

Thx for you help!


Solution

  • I found out, like spender mentioned already, the "issue" is the communication over SerialPort. I found an interesting topic here:

    SerialPort has a background thread that's waiting for events (via WaitCommEvent). Whenever an event arrives, it queues a threadpool work item that may result in a call to your event handler. Let's focus on one of these threadpool threads. It tries to take a lock (quick reason: this exists to synchronize event raising with closing; for more details see the end) and once it gets the lock it checks whether the number of bytes available to read is above the threshold. If so, it calls your handler.

    So this lock is the reason your handler won't be called in separate threadpool threads at the same time.

    Thats most certainly the reason why they arent collected immediatly. I also tried not using the blocking Read in my SendReceiveTelegram method, but using SerialDataReceivedEventHandler instead led to the same result.

    So for me, I will leave things now as they are, unless you bring me a better solution, where these ThreadPoolWorkitems arent kept that long in the Queue anymore.

    Thx for your help and also your negative assessment :-D