【问题标题】:What is the simplest way of moving some work to background thread?将一些工作移至后台线程的最简单方法是什么?
【发布时间】:2012-01-10 18:14:42
【问题描述】:

假设我有这段代码。

foreach(string value in myList)
  {
      string result = TimeConsumingTask(value);
      //update UI
      listBox.Add(result);
  }

我想将string result = TimeConsumingTask(value); 移动到某个后台线程。如果

- Order does matter
- Order doesn't matter

【问题讨论】:

  • 这是求职面试还是家庭作业?
  • myList 中字符串的顺序。假设我想按 myList 中的项目顺序更新列表框。第一个字符串的结果在列表框中排在第一位

标签: c# user-interface background-thread


【解决方案1】:

在诸如 WPF、WinForms、Silverlight 等 GUI 应用程序中,您可以使用 BackgroundWorker,因为它负责编组主线程上发生的不同回调,这在更新 UI 时很重要。在 ASP.NET 中你可以看看asynchronous pages

让我们举个例子,如果订单对后台工作人员很重要:

var worker = new BackgroundWorker();
worker.DoWork += (obj, args) =>
{
    args.Result = myList.Select(TimeConsumingTask).ToList();
};
worker.RunWorkerCompleted += (obj, args) =>
{
    var result = (IList<string>)args.Result;
    foreach(string value in result)
    {
        listBox.Add(result);
    }
}; 
worker.RunWorkerAsync();

【讨论】:

  • 您建议如何使用 BackgroundWorker 处理订单场景?
【解决方案2】:

保证顺序的一种简单方法是在同一个后台线程中按顺序执行所有 TimeConsumingTask。

这可能比在多个线程上执行多个 TimeConsumingTasks 花费的时间稍长,但在尝试同步结果并按所需顺序生成输出时可以省去很多麻烦。

如果您的主要目标是释放 UI 以便用户可以继续处理其他事情,那么使用一个线程和多个线程之间的完成时间差异可能可以忽略不计,因此请采用最简单的方法 - 使用单个后台线程对于所有 TimeConsumingTasks。

如果您的主要目标是使 TimeConsumingTasks 的计算更快地完成,那么运行多个线程可能是更好的方法,因为它更有可能利用多个内核并并行执行一些工作。

要在多线程情况下保持顺序,您必须自己对结果进行排序。您可以维护一个累加器临时列表并将每个结果插入到列表中结果的适当位置(如果位置/索引很容易计算或已知),或者在所有线程完成合并后添加一个额外的处理步骤多个结果以所需的顺序进入最终输出。在并行和分布式计算圈中,将一个问题拆分成多个块并行完成,然后将结果组合成一个连贯的顺序的过程称为“map reduction”。

【讨论】:

  • +1 你完全理解我并根据大局回答:) 当任务并行启动时,我正在寻找同步结果的东西。但我想那些脏活得靠手工来完成
  • 要将并行输出排序为操作被调度(未完成)的顺序,将每个项目的调度索引传递给每个并行任务,并将其用作将结果插入输出的索引列表。在进入 ParallelForEach 之前,需要计算调度索引并将其与任务输入值相关联,因为这可能会出现乱序。
  • 您可能还想查看 ParallelForEach 的 OrderablePartitioner 重载。
【解决方案3】:

有很多方法可以给这只猫剥皮。过去几年我用过很多,BackgroundWorker 是最常见和最直接的。但是从现在开始,您必须认为Async CTP 是要走的路。在决定之前至少review the intro。就简单、干净、简洁的代码而言,它是有史以来最热门的代码之一。 :)

【讨论】:

【解决方案4】:

我会使用一个任务。这样整个操作在后台运行而不会阻塞 UI。不过,请务必更新主线程上的列表框。我不确定如何在 WinForms 中执行此操作,但在 WPF 中您可以使用 Dispatcher.Invoke()Dispatcher.BeginInvoke() 来完成它,如下所示。

如果订购很重要:

Task.Factory.StartNew(
    () =>
        {
            foreach (string value in myList)
            {
                string result = TimeConsumingTask(value);
                //update UI
                Application.Current.Dispatcher.BeginInvoke(
                    (Action)(() => listBox.Add(result)));
            }            
        });

如果订购无关紧要,您也可以使用Parallel.ForEach()

Task.Factory.StartNew(
    () =>
        {
            Parallel.ForEach(myList, 
                value =>
                {
                    string result = TimeConsumingTask(value);
                    //update UI
                    Application.Current.Dispatcher.BeginInvoke(
                       (Action)(() => listBox.Add(result)));
                });            
        });
    }

【讨论】:

    【解决方案5】:

    您必须等待结果才能更新 UI,因此仅将 TimeConsumingTask() 移动到后台线程是没有意义的。您将需要从后台线程更新 UI(但将调用编组到 UI 线程)。

    这取决于您的最终目标,但一个简单的解决方案就是开始一项任务。如果顺序确实很重要,这将起作用。

    Task.Factory.StartNew(
        () => 
        {
            foreach(string value in myList)   
            {       
                string result = TimeConsumingTask(value);       
                listBox.Invoke(new Action(() => listBox.Add(result)));   
            } 
        });
    

    使用 BackgroundWorker 也很简单,但没有那么“简单”,因为代码更多:

    • 创建一个 BackgroundWorker
    • 分配一个 DoWork 处理程序
    • 分配进度处理程序
    • 设置 WorkerReportsProgress
    • 调用 RunWorkerAsync()

    【讨论】:

      【解决方案6】:

      如果顺序无关紧要,您可以使用Parallel.ForEach 循环。 (根据 dthorpe 和 ttymatty 的 cmets 编辑)。

      using System.Threading.Tasks;
      
      var results = new List<string>();
      
      Parallel.ForEach(myList, value =>
      {
          string result = TimeConsumingTask(value);
          results.Add(result);
      });
      
      //update UI
      listBox.AddRange(results);
      

      如果您想使用流程信息回调 UI,我建议您使用 BackgroundWorker,正如 Darin Dimitrov 在他的回答中所建议的那样。

      【讨论】:

      • 那个listBox.Add()不需要同步回UI线程吗?
      • 您无法更新 Parallel.ForEach 块内的 UI。相反,在 Parallel 块中构建一个 List 结果,然后在 Parallel 块之外,将结果添加到 UI。
      • @dthorpe 是的,我错过了那一点。感谢您的提示。
      • 您可以使用调度程序在 UI 线程上对 listBox 的更新进行排队。
      【解决方案7】:

      多线程和后台处理之间有很大的区别,尽管您可以同时使用它们。

      使用后台处理 -- 正如 Darin 建议的那样使用 BackgroundWorker -- 您可以允许用户继续在 UI 中工作,而无需等待后台进程完成。如果用户必须等待处理完成才能继续,那么在后台运行它是没有意义的。

      使用多线程 -- 或许使用Parallel Extensions Library -- 您可以使用多个线程并行运行重复循环,以便更快地完成。如果您的用户正在等待流程完成才能继续,这将非常有用。

      最后,如果你在后台运行某些东西,你可以使用多线程让它更快地完成。

      【讨论】:

        猜你喜欢
        • 2011-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-28
        相关资源
        最近更新 更多