2010-08-13 61 views
2

短缺问题采用这种多线程问题的方法是什么?

我想产生一个单一的后台线程会处理(有一个线程像一个线程池)提交到队列中的工作项目。一些工作项目能够报告进展情况,有些则不能。我应该使用.NET的无数多线程方法中的哪一种?


龙解释(避免询问有关half which doesn't make any sense):

我的WinForms应用程序的主窗口被垂直分割为两半。左半部分包含带有项目的树视图。当用户在树视图中双击一个项目时,该项目在右半部分打开。几乎所有的对象都有很多属性,分成几个部分(由制表符表示)。这些属性的加载需要相当长的时间,通常大约10s,有时更多。每隔一段时间就会添加更多的属性,所以时间会增加。

目前,我的单线程设计使UI在这个时候无法响应。自然这是不可取的。我想在背景中逐个加载东西,并且只要一个部件被加载就可以使用。对于其他部分,我会显示一个带有加载动画或其他东西的占位符选项卡。另外,虽然有些零件是在单一冗长的单片操作中加载的,但其他零件由许多较小的函数调用和计算组成,因此可能会显示加载进度。对于这些部件,看到进展情况会很好(特别是如果他们挂在某个地方,会发生什么情况)。

请注意,数据源不是线程安全的,所以我无法同时加载两个部分。

哪种方法最适合实现此行为?有没有一些.NET类可以解除我的肩膀上的一些工作,或者我应该沮丧地用Thread

A 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属性。如果用户更改节点,用户可以设置它。加载方法可以在整个操作过程中的各个位置检查中止值,如果已设置,则返回而不完成加载。

说实话,你选择的并不是很重要。所有三个选项都是在后台线程上完成的。如果我是你,我会开始使用BackgroundWorker,因为它很容易设置,如果你决定需要更多的东西,可以考虑切换到ThreadPool或纯线程。

BackgroundWorker还具有以下优点:您可以使用它的完成事件(在主UI线程上执行该事件)来使用加载的数据更新UI。

+0

是的,如果用户点击10个节点,他会等待。实际上,用户界面允许他打开几个项目并同时查看它们,这是一个有效的场景。但我同意,如果他*关闭了其中一项,则加载应该中止。为他目前正在观看的项目设置优先级也是一个很好的功能,没有想到这一点。所以是的,它变得混乱。我想那里不会有任何远程预制的东西。顺便说一句 - 你不觉得一个简单的'volatile int _current'应该是足够的,如果只有一个线程正在写入,并且只有一个线程正在读取它? – 2010-08-13 09:46:06

+0

叶,挥发性应该没问题。 – 2010-08-13 09:50:51

1

使用一个线程,在一个线程安全的集合放下你的工作,并使用调用当你更新你的UI做的正确的线程

2

.NET 4.0带来了很多的改进,通过引入Task类型多线程,它表示一个可能的异步操作。

对于您的情况,我建议将每个属性(或属性组)的加载分解为单独的任务。任务包括“父”的概念,因此每个对象的加载可以是拥有属性加载任务的父任务。

要处理取消操作,请使用新的统一取消框架。为每个对象创建一个CancellationTokenSource,并将其CancellationToken传递给父任务(将其传递给其每个子任务)。这允许一个对象被取消,它可以在当前加载属性完成后生效(而不是等到整个对象完成)。

要处理并发(或更合适的,并发),请使用ParallelExtensionsExtras sample library中的OrderedTaskScheduler。每个Task只代表需要安排的工作单元,并且通过使用OrderedTaskScheduler,确保顺序执行(在ThreadPool线程上)。

UI进度更新可以通过创建UI更新Task并将其调度到UI线程来完成。我有一个on my blog的例子,我把一些比较尴尬的方法换成ProgressReporter辅助类型。

Task类型的一个好处是,它以自然的方式传播异常和取消;这些往往是设计一个系统来处理像你这样的问题更困难的部分。

+0

有趣,将检查出来。 – 2010-08-13 10:58:46