Val*_*ale 1 c# multithreading backgroundworker
我正在使用 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);
}
}
Run Code Online (Sandbox Code Playgroud)
问题是线程完成后(通过“临界点”线后)内存没有被释放。我认为当线程不活动时,与它相关的所有资源都会被释放。但显然这不是真的。任何人都可以向我解释为什么以及我做错了什么。谢谢。
您设法关闭了组件,告诉您您做错了什么。但是,您实际上并没有解决问题。不支持线程的组件需要一个 STA,即单线程单元。这样它的所有方法都从同一个线程调用,即使调用是在另一个线程上进行的。COM 负责将调用从一个线程编组到另一个线程。STA 线程通过泵送消息循环使这成为可能。
但是,您所做的是创建另一个线程并对其进行调用,这与创建生成器对象的线程不同。这并不能解决问题,它仍然是线程不安全的。COM 仍对呼叫进行编组。
重要的是您在其上创建生成器对象的线程。由于它是一个单元线程对象,它必须在 STA 线程上创建。Windows 应用程序中通常只有一个,即程序的主线程,也就是通常所说的 UI 线程。如果您在非 STA 的 .NET 工作线程上创建它,就像您在这里所做的那样,那么 COM 将介入并创建一个 STA 线程本身,为组件提供一个好客的家。这很好,但通常是不可取的。
这里没有免费的午餐,你不能神奇地制作一大段代码,明确表示它(注册表中的 ThreadingModel 键)不支持线程的行为。下一个最佳选择是创建一个 STA 线程并在其上运行所有代码,包括创建 COM 对象。请注意,您通常必须使用 Application.Run() 泵送消息循环,许多 COM 服务器假定有一个可用。特别是当他们告诉您需要 STA 线程时。您会注意到它们在行为不端、在方法调用上死锁或不引发事件时会这样做。
关于您的原始问题,这是标准的 .NET 行为。垃圾收集器在需要时运行,而不是在您认为应该时运行。您可以使用 GC.Collect() 覆盖它,但这很少需要。尽管在您的情况下这可能是一个快速修复,但 COM 为每个文件创建一个新线程。STA 线程给生成器一个家。使用 Debug + Windows + Threads 来查看它们。这些线程不会停止,直到 COM 对象被销毁。这需要终结器线程运行。当有超过两千个文件时,您的代码还将消耗所有可用内存并因 OOM 爆炸,这可能足以寻找真正的修复程序。