解决这个多线程问题的方法是啥?
Posted
技术标签:
【中文标题】解决这个多线程问题的方法是啥?【英文标题】:Which approach to take with this multithreading problem?解决这个多线程问题的方法是什么? 【发布时间】:2011-03-29 08:56:08 【问题描述】:小问题:
我想生成一个后台线程来处理提交到队列的工作项(比如一个线程池)。有些工作项能够报告进度,有些则不能。我应该使用 .NET 的无数多线程方法中的哪一种?
详细解释(避免询问half which doesn't make any sense):
我的 winforms 应用程序的主窗口垂直分成两半。左半部分包含一个带有项目的树视图。当用户双击树视图中的一个项目时,该项目在右半边打开。几乎所有对象都有很多属性,分为几个部分(由选项卡表示)。这些属性的加载需要相当长的时间,通常在 10 秒左右,有时甚至更多。而且每隔一段时间就会添加更多属性,因此时间会增加。
目前我的单线程设计使 UI 暂时无响应。这自然是不可取的。我想在后台逐部分加载东西,一旦加载一部分就可以使用。对于其他部分,我会显示一个带有加载动画或其他内容的占位符选项卡。此外,虽然某些部分是在单个冗长的整体操作中加载的,但其他部分包含许多较小的函数调用和计算,因此可以显示加载进度。对于这些部分,很高兴看到进度(特别是如果它们挂在某个地方,这种情况发生了)。
请注意,数据源不是线程安全的,所以我不能同时加载两个部分。
什么方法最适合实现这种行为?是否有一些 .NET 课程可以减轻我的工作负担,还是我应该用Thread
搞砸?
ThreadPool
进行工作项队列管理,但没有用于进度报告的设施。另一方面,BackgroundWorker
支持进度报告,但它适用于单个工作项。是否可能两者兼而有之?
【问题讨论】:
【参考方案1】:听起来很棘手!
你说你的数据源不是线程安全的。那么,这对用户意味着什么。如果他们到处点击,但在点击其他地方之前不等待属性加载,他们可以点击需要很长时间加载的 10 个节点,然后在第 10 个节点上等待。由于数据源访问不是线程安全的,因此负载必须一个接一个地运行。这表明 ThreadPool 不是一个好的选择,因为它会并行运行负载并破坏线程安全。如果加载可以在中途中止,以防止用户在他们想要查看的页面开始加载之前必须等待最后 9 个节点加载,那将是很好的。
如果可以中止加载,我建议最好使用 BackgroundWorker。如果用户切换节点,并且 BackgroundWorker 已经很忙,请设置一个事件或其他东西来指示它应该中止现有工作,然后将新工作排队以加载当前页面。
另外,考虑一下,让在线程池中运行的线程报告进度并不是太棘手。为此,将进度对象传递给类似以下类型的 QueueUserWorkItem 调用:
class Progress
object _lock = new Object();
int _current;
bool _abort;
public int Current
get lock(_lock) return _current;
set lock(_lock) _current = value;
public bool Abort
get lock(_lock) return _abort;
set lock(_lock) _abort = value;
线程可以写入,ui 线程可以轮询(来自 System.Windows.Forms.Timer 事件)以读取进度并更新进度条或动画。
此外,如果您包含 Abort 属性。如果用户更改节点,ui 可以设置它。 load 方法可以在其运行过程中的各个点检查中止值,如果已设置,则在不完成加载的情况下返回。
老实说,您选择哪个并不重要。所有三个选项都在后台线程上完成工作。如果我是你,我会开始使用 BackgroundWorker,因为它的设置非常简单,如果你决定需要更多东西,请考虑在之后切换到 ThreadPool 或普通 Thread。
BackgroundWorker 的另一个优点是您可以使用它的完成事件(在主 ui 线程上执行)来使用已加载的数据更新 ui。
【讨论】:
是的,如果用户点击了10个节点,他就等待。实际上,UI 允许他打开多个项目并同时查看它们,所以这是一个有效的场景。但我同意如果他关闭其中一项,加载应该中止。为他当前正在查看的项目设置优先级也是一个不错的功能,没想到这一点。所以,是的,它变得混乱。我想不会有任何远程预制的东西。顺便说一句 - 如果只有一个线程正在写入并且只有一个线程正在读取它,您不认为一个简单的volatile int _current
就足够了吗?
是的,volatile 应该没问题。【参考方案2】:
使用线程,将您的工作放在线程安全的集合中,并在您更新 ui 以在正确的线程中执行时使用调用
【讨论】:
【参考方案3】:.NET 4.0 通过引入Task
类型对多线程进行了很多改进,该类型表示单个可能的异步操作。
对于您的方案,我建议将每个属性(或属性组)的加载拆分为单独的任务。任务包含“父”的概念,因此每个对象的加载都可以是拥有属性加载任务的父任务。
要处理取消,请使用新的统一取消框架。为每个对象创建一个CancellationTokenSource
并将其CancellationToken
传递给父任务(将其传递给它的每个子任务)。这允许取消一个对象,这可以在当前加载的属性完成后生效(而不是等到整个对象完成)。
要处理并发(或更准确地说,非-并发),请使用ParallelExtensionsExtras sample library 中的OrderedTaskScheduler
。每个Task
只代表一个需要调度的工作单元,通过使用OrderedTaskScheduler
,您可以确保顺序执行(在ThreadPool 线程上)。
可以通过创建 UI 更新 Task
并将其调度到 UI 线程来完成 UI 进度更新。我有一个 on my blog 的示例,我将一些更笨拙的方法包装到 ProgressReporter
辅助类型中。
Task
类型的一个优点是它以自然的方式传播异常和取消;这些通常是设计一个系统来处理像你这样的问题的更困难的部分。
【讨论】:
有趣,会去看看。以上是关于解决这个多线程问题的方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章