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

时间:2010-08-13 09:09:03

标签: .net winforms multithreading backgroundworker task-queue

简短问题

我想生成一个后台线程来处理提交到队列的工作项(比如一个线程的线程池)。一些工作项目能够报告进度,有些则不能。我应该使用哪种.NET无数的多线程方法?


长解释(以避免询问half which doesn't make any sense):

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

目前,我的单线程设计使得此时UI无响应。当然这是不可取的。我想在后台逐个加载东西,一旦加载了一部分就可以使用它。对于其他部分,我会显示带有加载动画的占位符选项卡。此外,虽然某些部件在一个冗长的单片操作中加载,但其他部件由许多较小的函数调用和计算组成,因此可以显示加载进度。对于这些部件,很高兴看到进度(特别是如果它们挂在某处,会发生这种情况)。

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

哪种方法最适合实施此行为?是否有一些.NET课程可以解除我的肩膀上的一些工作,或者我应该对Thread感到沮丧?

ThreadPool执行工作项队列管理,但没有用于进度报告的工具。另一方面,BackgroundWorker支持进度报告,但它适用于单个工作项。是否有两者兼而有之?

3 个答案:

答案 0 :(得分:2)

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

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

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

要处理并发(或更恰当地说, - 并发),请使用ParallelExtensionsExtras sample library中的OrderedTaskScheduler。每个Task仅代表需要安排的工作单元,并且通过使用OrderedTaskScheduler,您可以确保顺序执行(在ThreadPool线程上)。

可以通过创建UI更新Task并将其安排到UI线程来完成UI进度更新。我有一个这个on my blog的例子,我把一些更笨拙的方法包装成ProgressReporter助手类型。

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

答案 1 :(得分:1)

听起来很棘手!

您说您的数据源不是线程安全的。那么,这对用户意味着什么呢。如果他们在整个地方点击,但是在点击其他地方之前不要等待属性加载,他们可以点击10个节点,这需要很长时间才能加载,然后等待10号节点。由于数据源访问不是线程安全的,因此负载必须一个接一个地运行。这表明ThreadPool不是一个好选择,因为它会并行运行负载并破坏线程安全性。如果负载可以在中途中止以防止用户在他们想要看到的页面开始加载之前必须等待最后9个节点加载,那将是一件好事。

如果可以中止加载,我建议最好使用BackgroundWorker。如果用户切换节点,并且BackgroundWorker已经忙,请设置一个事件或其他信号,表示它应该中止现有工作,然后排队新工作以加载当前页面。

另外,请注意,使线程池中的线程运行报告进度并不太棘手。为此,将progress对象传递给类似这样的类型的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或普通线程。

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

答案 2 :(得分:1)

使用线程,将您的工作放入线程安全集合中,并在更新ui时使用invoke以在正确的线程中执行

相关问题