.net 使用者线程垃圾回收问题 出身背景

.net 使用者线程垃圾回收问题 出身背景,.net,multithreading,garbage-collection,.net,Multithreading,Garbage Collection,我已经实现了支持多个消费者线程和多个生产者的消费者生产者模式 消费者等待着一个脉搏来竞争一份工作: private void DistributedConsume() { while (_continueConsuming) { try { Monitor.Wait(_workerLockObject); IModelJob job = _jobProvider.GetNextJob();

我已经实现了支持多个消费者线程和多个生产者的消费者生产者模式

消费者等待着一个脉搏来竞争一份工作:

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);

}
正在执行的作业可能会消耗相当大的内存,但正如您所看到的,该作业是使用内部作用域声明的,因此我希望该作业在每个循环中都超出作用域,因此它及其所有引用的数据都符合垃圾收集的条件

问题 作业执行,消耗大量内存,作业完成,使用者线程循环回监视器。等待并等待脉冲。在此阶段,作业已超出范围,不再被引用。问题是,如果没有新作业排队,线程也没有脉冲,那么内存使用率就会非常高——不管我等待多长时间。使用WinDbg,我还可以看到堆上的作业及其引用的所有子体对象

这可能会让您认为我有内存泄漏,但WinDbg也会验证作业对象是否没有根。但是,一旦我们向系统提交了另一个作业,并且该消费者线程拾取了该作业,内存就会被释放,对象就会被清理。但如果其他消费者选择了这项新工作,内存似乎没有释放

我搞不懂这个。我对可能发生的事情的偏执理论都与我对G.C.如何运作的理解不符

理论1。作业已到达第1代或第2代,因此除非系统内存不足,否则不会收集作业。但是,当线程被唤醒并开始执行另一个作业时,它就会收集它,这是没有意义的,因为内存远远不够用

理论2。我没有第二个理论值得分享

当系统忙于许多自动化作业时,消费者不会坐在那里等太久,所以问题就看不见了。但在安静的日子里,只有用户手动提交一些大作业,人们开始质疑为什么这项服务在如此高的内存使用率下处于空闲状态。因此,这不是一个关键问题,但令人困惑

有什么建议吗


谢谢

您是否使用任务管理器来测量内存使用情况? 如果是这样的话,用真正的记忆工具(比如ANTS)进行测试。 任务管理器往往会夸大内存使用情况

工作超出范围? 这对我来说确实是一个参考:

IModelJob job = _jobProvider.GetNextJob();
这两种说法是错误的: 内部作用域,因此我希望作业在每个循环中都超出作用域 在此阶段,作业已超出范围,不再被引用。 在当前格式中,只有在退出时作业才会超出范围,而不是在继续使用时循环 上一个IModelJob作业只有在下次到达该行时才会超出范围 这正是你所看到的行为

将IModelJob作业放入使用块中 这样,它将在循环中超出范围

using (IModelJob job = _jobProvider.GetNextJob()) 
{

}
请尝试以下方法 但不要将GC收集留在生产中

if (job != null)
{
...
}
GC.Collect();


using (IModelJob job = _jobProvider.GetNextJob()) 
{

}
GC.Collect();
旧链接:


在使用GC时,以及在监视任何托管应用程序的内存使用情况时,需要记住的关键事项是:

对象的内存不会被垃圾收集,因为它们超出了范围

换句话说:如果您没有对对象的更多引用,这并不意味着该对象已被收集。这意味着它有资格被收集,并将在下次GC运行时被收集。不过,你不能保证什么时候会这样

在.NET应用程序中,GC将作为内存分配过程的一部分自动运行:也就是说,如果您尝试创建一个新对象,而Gen0中没有该对象的空间,GC将运行以释放所需的空间。如果不进行分配,则不会发生收集。有一些罕见的例外;例如:最小化WinForms应用程序会释放GC并释放未引用的内存,尽管从.NET2开始我就没有测试过这种行为

在您的情况下,假设您的代码编写正确并且不包含对相关对象的任何引用,这就是预期的行为:您的代码正在等待脉冲,不会进行任何分配,因此GC没有压力,不太可能运行收集。当您开始下一个作业时,您就开始分配内存,因此GC很快就会跟进并收集上一个作业中不再引用的内容


所以我想说理论2是:GC正在做它期望做的事情,没有什么可担心的。

在处理块之后,你也可以打电话给垃圾收集器,礼貌地要求它清理,尽管它完全保留在不起作用的时候无视你的请求继续玩皮诺奇勒或其他任何游戏的权利。@MauriceReeves是的。但我不认为强制收集是一种好的做法。我通常也会尽量避免,但在这种情况下,这可能不是一个坏主意。@MauriceReeves当系统每天忙于许多自动化作业时
梅尔不会坐着等太久。需要时让它收集。强制收集的多个使用者可能是错误的。@Mauricereeve给出了一个小注释:在Java中,System.gc是对运行时进行垃圾收集的提示。在.NET中,至少根据MSDN,GC.Collect强制进行垃圾收集。当发布的代码明确包含对相关对象的引用时,为什么您认为代码不包含对相关对象的任何引用?对象根本不在范围之外。看我的答案。我不知道,但我是说假设是这样的话,这个问题仍然基于一个无效的前提。即使考虑到你的答案,我也不会期望行为改变,除非应用程序中其他地方的分配压力导致GC。但我也会反驳你的答案是错误的。稍后将进行注释。BUM-连续监视器。在while内等待,try表示上一个作业变量超出范围,并且每次都会重新声明。如果这是你的意思的话,当然它在try里面有一个引用。丹-谢谢你的指点。我错误地认为,在系统内存受到压力之前,G.C不会运行,但您关于当Gen 0无法分配所需内存时将触发收集的观点提供了一些明确性,同时指出获得作业的脉冲线程将是请求内存的线程。我将重新配置并确认。