2

简短的问题

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


冗长的解释(避免询问没有任何意义的一半):

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

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

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

什么方法最好实现这种行为?是否有一些 .NET 课程可以减轻我的工作负担,或者我应该放下并弄脏 .NETThread吗?

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

4

3 回答 3

2

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

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

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

要处理并发(或更准确地说,并发),请使用OrderedTaskSchedulerParallelExtensionsExtras示例库中的。每个Task只代表一个需要调度的工作单元,通过使用OrderedTaskScheduler,您可以确保顺序执行(在 ThreadPool 线程上)。

UI 进度更新可以通过创建 UI 更新Task并将其调度到 UI 线程来完成。我的博客上有一个这样的例子,我将一些更笨拙的方法包装成一个ProgressReporter辅助类型。

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

于 2010-08-13T10:25:22.827 回答
1

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

于 2010-08-13T09:43:12.667 回答
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。

于 2010-08-13T09:24:34.407 回答