我正在使用BackgroundWorker,在其中我使用foreach循环,在其中创建新线程,等待它完成,然后报告进度并继续foreach循环。这就是我所说的:
private void DoWork(object sender, DoWorkEventArgs e) {
var fileCounter = Convert.ToDecimal(fileNames.Count());
decimal i = 0;
foreach (var file in fileNames) {
i++;
var generator = new Generator(assembly);
var thread = new Thread(new ThreadStart(
delegate() {
generator.Generate(file);
}));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
while (thread.IsAlive); // critical point
int progress = Convert.ToInt32(Math.Round(i / fileCounter * 100));
backgroundWorker.ReportProgress(progress);
}
}
问题是线程完成后(在通过“临界点”行之后)没有释放内存。我认为当线程不活动时,所有与之关联的资源都将被释放。但显然这不是真的。 任何人都可以向我解释为什么以及我做错了什么。 感谢。
答案 0 :(得分:4)
你成功地关闭了组件,告诉你你做错了什么。但是你没有真正解决问题。不支持线程的组件需要STA,Single Threaded Apartment。所以它的所有方法都是从相同的线程调用的,即使调用是在另一个线程上进行的。 COM负责将调用从一个线程编组到另一个线程。 STA线程通过泵送消息循环使其成为可能。
然而,您所做的是创建另一个线程并对其进行调用,这与创建 generator 对象的线程不同。这并没有解决问题,它仍然是线程不安全的。 COM仍在召集电话。
重要的是您创建生成器对象的线程。由于它是一个单元线程对象,因此必须在STA线程上创建 。 Windows应用程序中通常只有一个,即程序的主线程,通常称为UI线程。如果你在不是STA的.NET工作线程上创建它,就像你在这里一样,那么COM将介入并创建一个STA线程本身,为组件提供一个好客的家。这很好,但通常是不受欢迎的。
这里没有免费的午餐,你不能神奇地制作明确表示它没有的代码(注册表中的ThreadingModel键)支持线程行为就像它一样。您的下一个最佳选择是创建一个STA线程并在其上运行所有代码,包括创建COM对象。请注意,您通常必须使用Application.Run()泵送消息循环,许多COM服务器假设有一个可用。特别是当他们告诉你需要一个STA线程时。你会注意到他们在行为不端时会这样做,在方法调用上遇到僵局或者没有引发事件。
关于您的原始问题,这是标准的.NET行为。垃圾收集器在需要时运行,而不是在您认为应该运行时运行。您可以使用GC.Collect()覆盖它,但这很少需要。虽然在您的情况下它可能是一个快速修复,但COM为每个文件创建一个新线程。 STA线程为 generator 提供一个home。使用Debug + Windows + Threads查看它们。在销毁COM对象之前,这些线程不会停止。这需要终结器线程运行。当有超过两千个文件时,你的代码也将消耗所有可用的内存并使用OOM炸弹,或许有足够的理由寻找真正的修复。
答案 1 :(得分:1)
这可能导致垃圾收集不是立竿见影的。在线程超出范围后尝试收集:
编辑:
您还需要实现一种更好的方法来等待线程完成其他忙碌等待(while (thread.IsAlive);
)以节省CPU时间,您可以使用AutoResetEvent
。
private void DoWork(object sender, DoWorkEventArgs e) {
var fileCounter = Convert.ToDecimal(fileNames.Count());
decimal i = 0;
var Event = new AutoResetEvent(false);
foreach (var file in fileNames) {
i++;
var generator = new Generator(assembly);
{
var thread = new Thread(new ThreadStart(
delegate() {
generator.Generate(file);
Event.Set();
}));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
//while (thread.IsAlive); // critical point
Event.WaitOne();
}
GC.Collect();
int progress = Convert.ToInt32(Math.Round(i / fileCounter * 100));
backgroundWorker.ReportProgress(progress);
}
}
答案 2 :(得分:0)
生成方法从UI控件获取数据吗?