C# BackgroundWorker 在重启时占用内存
C# BackgroundWorker eats memory on restart
我用以下代码创建了一个空表单来测试不断重启后台工作程序的性能;
public Form1()
{
InitializeComponent();
BackgroundWorker bw = new BackgroundWorker();
// Do work on Background thread
bw.DoWork += DoWork_WorkerThread;
bw.RunWorkerAsync();
// Return to UI thread and Restart the worker
bw.RunWorkerCompleted += DoWork_WorkerThreadCompleted;
}
private void DoWork_WorkerThread(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(100);
//GC.Collect();
}
private void DoWork_WorkerThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Restart the worker.
// We can verify its the background worker because commenting this out relieves the issue.
((BackgroundWorker)sender).RunWorkerAsync();
}
您可以在任务管理器中看到,每次从 DoWork_WorkerThreadCompleted
重新启动工作程序时,内存都会跳起来。
我注意到,如果我将 GC.Collect()
扔进工作线程,问题就会消失。我是否错误地重新启动了工作人员,或者有没有办法确保在不调用 GC.Collect()
的情况下处理后台线程?
感谢您的宝贵时间。
只有少数用例需要 显式 调用 GC。仅仅因为内存在重复操作时增加并不一定意味着存在内存泄漏。 .NET for one 仅在真正需要时才释放内存,例如内存压力高时。调用 GC.Collect
只是强制问题。
注释掉 worker start 并不能真正证明什么。虽然是的,在这种情况下你已经阻止了内存增加,但你也消除了在 worker 运行 完成时释放内存的可能性,并允许有足够的时间或事件再次释放内存。 您永远无法真正准确地预测 GC 何时会发生。 无法承受调用 GC.Collect
。
你可以换个角度想问题。想象一下分配一个你知道使用合理数量 RAM 的对象。
void DoSomething()
{
var ob = new MyObjectRequiringReasonableAmountOfRam();
// 'ob' goes out of scope and is now a candidate for GC at some time or other
}
现在调用此方法,对象已分配并超出范围,是的,内存可能会增加,但认为存在内存泄漏还为时过早。当然,注释掉代码会消除 "leak" 的症状,但它也会像注释掉您的工作线程一样删除功能。
现在,如果除了调用上述方法外,我还调用:
void DoSomethingSlightlyMoreDrastic()
{
var ob = new MyObjectRequiringALargerAmountOfRam();
// 'ob' goes out of scope and is now a candidate for GC at some time or other
}
.....NET 看到此分配需要更多内存并考虑到您系统上的所有其他因素,例如您的进程分配了多少内存;来自所有其他进程的内存;您计算机中的 RAM 和 GC 的工作规则,此时您的第一个对象完全有可能被 GC。
我用以下代码创建了一个空表单来测试不断重启后台工作程序的性能;
public Form1()
{
InitializeComponent();
BackgroundWorker bw = new BackgroundWorker();
// Do work on Background thread
bw.DoWork += DoWork_WorkerThread;
bw.RunWorkerAsync();
// Return to UI thread and Restart the worker
bw.RunWorkerCompleted += DoWork_WorkerThreadCompleted;
}
private void DoWork_WorkerThread(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(100);
//GC.Collect();
}
private void DoWork_WorkerThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Restart the worker.
// We can verify its the background worker because commenting this out relieves the issue.
((BackgroundWorker)sender).RunWorkerAsync();
}
您可以在任务管理器中看到,每次从 DoWork_WorkerThreadCompleted
重新启动工作程序时,内存都会跳起来。
我注意到,如果我将 GC.Collect()
扔进工作线程,问题就会消失。我是否错误地重新启动了工作人员,或者有没有办法确保在不调用 GC.Collect()
的情况下处理后台线程?
感谢您的宝贵时间。
只有少数用例需要 显式 调用 GC。仅仅因为内存在重复操作时增加并不一定意味着存在内存泄漏。 .NET for one 仅在真正需要时才释放内存,例如内存压力高时。调用 GC.Collect
只是强制问题。
注释掉 worker start 并不能真正证明什么。虽然是的,在这种情况下你已经阻止了内存增加,但你也消除了在 worker 运行 完成时释放内存的可能性,并允许有足够的时间或事件再次释放内存。 您永远无法真正准确地预测 GC 何时会发生。 无法承受调用 GC.Collect
。
你可以换个角度想问题。想象一下分配一个你知道使用合理数量 RAM 的对象。
void DoSomething()
{
var ob = new MyObjectRequiringReasonableAmountOfRam();
// 'ob' goes out of scope and is now a candidate for GC at some time or other
}
现在调用此方法,对象已分配并超出范围,是的,内存可能会增加,但认为存在内存泄漏还为时过早。当然,注释掉代码会消除 "leak" 的症状,但它也会像注释掉您的工作线程一样删除功能。
现在,如果除了调用上述方法外,我还调用:
void DoSomethingSlightlyMoreDrastic()
{
var ob = new MyObjectRequiringALargerAmountOfRam();
// 'ob' goes out of scope and is now a candidate for GC at some time or other
}
.....NET 看到此分配需要更多内存并考虑到您系统上的所有其他因素,例如您的进程分配了多少内存;来自所有其他进程的内存;您计算机中的 RAM 和 GC 的工作规则,此时您的第一个对象完全有可能被 GC。