我已经实现了支持多个消费者线程和多个生产者的消费者 - 生产者模式。
消费者在等待冲动以争取工作:
private void DistributedConsume()
{
while (_continueConsuming)
{
try
{
Monitor.Wait(_workerLockObject);
IModelJob job = _jobProvider.GetNextJob();
if (job != null)
{
//we found and dequeued job for processing
if (job.JobType != JobType.TerminateWorker)
{
job.PrioritizationStatus = PrioritizationStatus.Dequeued;
try
{
job.Execute();
}
catch (ThreadAbortException)
{
LoggerFacade.Log(LogCategory.Debug, "WorkerController.DistributedConsume(): Thread {0} has been aborted", Thread.CurrentThread.Name);
throw;
}
catch (Exception ex)
{
LoggerFacade.Log(LogCategory.Debug, "WorkerController.DistributedConsume(): Unhandled exception on thread {0}", Thread.CurrentThread.Name);
}
}
else
{
_continueConsuming = false;
}
}
}
catch (ThreadAbortException)
{
LoggerFacade.Log(LogCategory.Debug, "WorkerController.DistributedConsume(): Thread {0} has been aborted", Thread.CurrentThread.Name);
throw;
}
catch (Exception ex)
{
ExceptionHandler.ProcessException(ex, "Unexpected Exception occured attempting to fetch the next job");
}
}
LoggerFacade.Log(LogCategory.OperationalInfo, "WorkerController.DistributedConsume(): Thread {0} is exiting", Thread.CurrentThread.Name);
}
正在执行的作业可能会消耗相当多的内存,但正如您所看到的那样,作业是使用内部作用域声明的,因此我希望作业在每个循环中超出范围,因此它及其所有引用的数据将有资格进行垃圾收集。
作业执行,消耗大量内存,作业完成,消费者线程循环回Monitor.Wait并等待脉冲。在此阶段,作业超出范围,不再引用。有问题的是,如果没有新作业排队且线程没有脉冲,则内存使用率会保持很高 - 无论我等待多久。使用WinDbg我还可以看到堆上的作业以及它引用的所有后代对象。
这可能会让您认为我有内存泄漏,但WinDbg还会验证作业对象不 root。但是,只要我们向系统提交另一个此消费者线程的作业,就会释放内存并清理对象。但如果其他消费者接受了这项新工作,那么内存就不会被释放。
我无法理解这一点。我对可能发生的事情的偏执理论都不符合我对G.C如何运作的理解。
理论1.这项工作已经成为第1代或第2代,因此除非系统缺乏记忆,否则不会被收集。但是,一旦线程被唤醒并且开始执行另一项工作,因为内存远未达到低位,它就会收集它是没有意义的。
理论2.我没有第二个理论值得分享。
当系统忙于许多自动化工作时,消费者不会等待太长时间,因此问题不可见。但是在安静的日子里,只有用户手动提交一些大型工作的问题正在被提出,为什么服务处于空闲状态并具有如此高的内存使用率。因此,这不是一个关键问题,但它令人困惑。
任何指针?
由于
答案 0 :(得分:0)
您是否使用任务管理器来衡量内存使用情况? 如果是这样的话,用像ANTS这样的真实记忆工具进行测试 任务管理器将倾向于夸大内存使用情况。
工作超出范围?
这看起来像是对我的引用:
IModelJob job = _jobProvider.GetNextJob();
这两个陈述是错误的:
“内部范围,所以我希望这个工作超出每个循环的范围”
“在这个阶段,这项工作超出了范围,不再被引用。”
在当前格式作业中,当您退出(而不是循环)while(_continueConsuming)时,该作业仅超出范围
以前的IModelJob作业只有在下次打到该行时才会超出范围
这正是你所看到的行为
将IModelJob作业放入使用块中 这样它就会超出范围IN循环
using (IModelJob job = _jobProvider.GetNextJob())
{
}
请尝试以下
但是不要将GC收集到生产中。
if (job != null)
{
...
}
GC.Collect();
using (IModelJob job = _jobProvider.GetNextJob())
{
}
GC.Collect();
旧链接:
答案 1 :(得分:0)
使用GC时,以及当您监视任何托管应用程序的内存使用情况时,要记住的关键是:
您的对象的内存不会被垃圾收集,因为它们超出了范围。
换句话说:如果你没有对某个对象的引用,不意味着该对象已被收集。这意味着它符合条件进行收集,并且将在下次GC运行时收集。但是,你不能保证什么时候会这样。
在.NET应用程序中,GC将作为内存分配进程的一部分自动运行:也就是说,如果您尝试创建新对象,并且Gen0中没有该对象的空间, GC将运行以释放所需的空间。如果您没有分配,则不会发生任何收集。 (有一些罕见的例外;例如:最小化WinForms应用程序将GC和释放未引用的内存,尽管我还没有测试过这种行为,因为可能是.NET 2。)
在你的情况下(假设你的代码是正确编写的并且没有对相关对象进行任何引用),这是预期的行为:你的代码正在等待Pulse
而不会进行任何分配,因此GC没有压力,也不太可能运行集合。当你开始下一份工作时,你开始分配内存,因此很快就会跟进GC,并收集上次工作中不再引用的内容。
所以我会说"理论2"是:GC正在完成预期的工作,并且没有什么可担心的。