我应该使用 BackgroundWorker 还是 Threads 来完成这个任务?

5
大家好,我之前在一些小规模应用程序中成功地使用了 ThreadBackgroundWorker 类来促进流畅的用户界面。最近,我被分配了一个巨大的代码转换工作,需要将串行代码转换为多线程代码。由于我在这个网站上看到了一些评论,所以我有一些问题需要解决。我需要转换的代码会调用 SQL Server 进行各种查询,查询次数不定(通常都很大),这些 SQL 查询有时需要运行 30 分钟左右。因此,需要使用多线程技术。
我已经使用 BackgroundWorker 设置了一个测试程序,并且运行良好。然而,有些人说由于 BackgroundWorker 使用线程池,因此不应该用于长时间运行的任务。我没有在任何地方看到过这样的说法(例如 Joesph Albahari 的 C# 4.0 In a Nutshell),而这与 MSDN 相矛盾。那么,我应该使用 BackgroundWorker 还是 Thread 来进行这样的操作呢?
谢谢!

您是否考虑使用任务并行库? - Johann Blais
2
BackgroundWorker是可以的。但是运行SQL查询需要30分钟太长了 - 你应该优先考虑减少它的时间。 - rudolf_franek
我可能是指“托管”线程池。也就是说,使用这个“托管线程池”的BackgroundWorker将使我能够集中精力处理应用程序任务,而不是线程管理。我很感激30分钟的时间,但是对于这样的任务使用上述两个选项似乎并不明确,我希望以最佳方式实现多线程,这也被普遍认为是“最佳实践”。 - MoonKnight
@Johann Blais。我没有考虑TPL,因为大多数最终用户将在桌面上运行。尽管大多数桌面都是多核的,但不能保证(虽然这并不意味着TPL不能为串行机器伪并行化代码,但这并不会提供任何速度上的好处)。此外,应用程序正在执行的工作类型通常由用户脚本编写。对于一些小规模用户来说,并行化的行为会减慢执行速度。 - MoonKnight
1
@Killercam,另外TPL的默认行为是在线程池上运行任务。 :) - Ilian
显示剩余2条评论
4个回答

5

哇!我不知道你可以这样做。这非常有用。感谢您的帖子...尽管我的线程问题仍然存在,因为不仅执行 SQL 工作,我仍需要一个独立的线程来保持 UI 不会冻结。再次感谢。 - MoonKnight
的确,这很有用,可以减少所需的线程数量。然而,正如其他用户建议的那样,30分钟对于一个查询来说确实太长了。也许查看查询计划可以帮助找到索引候选项或瓶颈。 - H-Man2
我知道这段内容很长,但问题并不在于此。我们提供的数据有时会有数百万行的多列。这只是目前的情况,并不是由于数据库或查询构造不良导致的。用户需要的聚合级别意味着我们有时必须以这种方式处理这些数据库。附注:我们在这些表上使用索引,它们确实大大加快了操作速度。谢谢。 - MoonKnight

3

线程池的概念是重用线程,从而分摊创建新线程的成本。创建一个线程可能是一个非常昂贵的操作,所以如果可能的话,重用线程是很有意义的。如果您在线程池上安排长时间运行的作业,您将迫使它创建额外的线程,从而减少重用线程的好处。


2

BackgroundWorker适用于长时间运行的任务。而线程池中的线程不应该用于长时间运行的任务(BackgroundWorker不使用线程池线程)。

此外,30分钟的SQL查询绝对是一种代码异味。您可能需要调查一下问题所在并查看是否可以加速。

编辑:我错了 - BackgroundWorker 确实使用线程池线程。我会保留这个答案以供下面讨论。


1
BackgroundWorker在内部调用BeginInvoke,我认为它运行在线程池上。 - Ilian
1
@Ilian:你可能是对的,虽然当我谷歌这个问题时,我无法找到任何明确的、权威的(即来自微软的)说明。如果BackgroundWorker使用线程池线程,则广泛使用BGW线程处理长时间运行的任务的建议是错误的。 - MusiGenesis
BackgroundWorker确实使用 托管线程池 请参见此处。然而,上述链接中的语句(即“有几种情况适合创建和管理自己的线程,而不是使用线程池线程:...您有任务导致线程长时间阻塞...")与此MSDN链接中的前几行所提出的观点相矛盾-这就是我的问题所在,整个问题似乎很主观。 - MoonKnight
没错。这完全是冲突的。我不想使用错误的方法进行一万到一万五千行代码的翻译,因为这不被认为是最佳实践。请注意:由于某些SQL任务将为24百万行以上的数据集填充10列左右的数据(取决于用户的要求),因此查询执行所需的时间很长。 - MoonKnight
1
我建议您使用SqlCommand.BeginExecuteXxxx()。完全不需要线程。由于您无法确定查询需要多长时间,因此无法提供可靠的进度。使用跑马灯模式的进度条来提供“我还活着”的反馈。 - Hans Passant
显示剩余6条评论

1

ThreadPool具有活动线程的最大数量(GetMaxThreads/SetMaxThreads)。超过该数字的任何任务都将保持排队状态,直到ThreadPool线程可用。为了举例说明,假设最大线程数为10。如果您通过BackgroundWorker生成10个查询,并且每个查询在30分钟内完成,则在ThreadPool上排队的任何其他任务都将在30分钟后才能运行。

该任务可能只是一个微不足道的来自Timer的滴答声,它更新您在UI上拥有的时钟。这可能会导致问题,因为该时钟可能会被卡住,直到有一个线程可用为止。但实际上,MaxThreads肯定大于10。我的是1023(.NET 4, 2x2.26GHz笔记本电脑)。因此,如果您决定坚持使用BackgroundWorker,希望您不会遇到此问题。尽管如此,了解为什么通常不建议在ThreadPool上运行长时间运行的任务仍然很有用。

个人认为还是使用专门的线程比较保险。特别是当这些线程在等待查询完成时肯定会处于空闲状态。在您的情况下,我看到使用 BackgroundWorker 的唯一优点是更容易在 UI 上更新进度条。但如果您不显示查询的进度,那就是另一个使用专用线程的原因。


感谢您的时间。SQL查询由脚本处理器按顺序处理。这意味着我每次只会运行一个查询(一个 SQL 查询的结果将为随后的查询提供数据)。考虑到您关于 BGW 和 UI 更新的建议,我很可能会选择 BGW。不过,Hans Passant 提出了使用 ADO.NET 和 SqlCommand.BeginExecuteXxxx() 进行 SQL 操作的好点子。再次感谢。 - MoonKnight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接