修改
好消息是下面解释的奇怪行为与ConcurrentBag无关,即:与并发包相关的线程最终被释放。然而,由于某些或其他原因,自己的线程仍然存在。在给出的示例代码中,我清楚地创建了一个线程并销毁所有引用。然而,垃圾收集不会捡起它。实际上,到目前为止,我发现线程完全被破坏的唯一时刻是并发包本身被收集(如果我不收集并发包,线程将保持活动状态),或者当我们创建许多其他线程时
ORIGINAL(原始问题和一些动机。此部分下面的源代码解释了重要方面)
这是我几个月前就ConcurrentBag(Possible memoryleak in ConcurrentBag?)问过的一个问题的复制品。显然,ConcurrentBag的行为并不应该如此,现在我担心一些正在运行的遗留代码的稳定性。在回答以下问题时,这个问题是对我的发现的回应:How can I free-up memory used by a Parallel.Task?
情景没有改变。我有一个处理消息的web服务。客户端可以使用一些公共API在消息中进行拍摄。这些请求将由来自.NET ThreadPool的线程进行处理,而ThreadPool又将消息添加到ConcurrentBag。接下来,有并发任务消耗来自ConcurrentBag的消息并处理它们。有高峰时段添加了许多消息,许多消息被消耗,有时没有人做任何事情。因此,线程池本身在运行时间内会非常广泛地改变其活动线程的数量(期望的运行时间是'永远')。
现在事实证明,一旦线程调用ConcurrentBag.Add(或ConcurrentBag上的任何方法)。通过ConcurrentBag内部保存的引用保持线程活动,并且只有当GC实际清除concurrentBag本身时才释放该线程。在我的场景中,这将导致无数的“浪费”线程,因为ConcurrentBag在整个应用程序的生命周期中都处于活动状态,所以它们不会随着时间的推移而被清理。
之前给出的简单清空袋子的解决方案也无济于事,因为没有问题。问题(可能是ConcurrentBag没有调用它为当前Thread保存的ThreadLocal上的Dispose,因为ConcurrentBag无法知道线程何时结束)。 (但是每当你再次进入行李时它应该进行清理。)
那就是说,我们是否应该在大多数情况下停止使用ConcurrentBag,或者我是否可以添加解决方案以解决此问题?
我的测试代码:
Action collectAll = () =>
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
};
ConcurrentBag<object> bag = new ConcurrentBag<object>();
Thread producerThread = new Thread(new ThreadStart(delegate
{
// produce 1 item
bag.Add(new object());
}));
// create a weakreference to the newly created thread so we can track its status
WeakReference trackingReference = new WeakReference(producerThread);
// start the thread and let it complete
producerThread.Start();
producerThread.Join();
// thread can now be set to null, after a full GC collect, we assume that the thread is gone
producerThread = null;
collectAll();
Console.WriteLine("Thread is still alive: " + trackingReference.IsAlive); // returns true
// consume all items from the bag and collect again, the thread should surely be disposed by now
object output;
bag.TryTake(out output);
collectAll();
Console.WriteLine("Thread is still alive: " + trackingReference.IsAlive); // returns true
// ok, finally remove all references to the Bag
bag = null;
collectAll();
Console.WriteLine("Thread is still alive: " + trackingReference.IsAlive); // returns false