3

假设我有这段代码。

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
4

7 回答 7

3

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

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

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();
于 2012-01-10T18:17:40.820 回答
2

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

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

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

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

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

于 2012-01-10T18:45:50.360 回答
1

您必须等待结果才能更新 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()
于 2012-01-10T18:23:57.573 回答
1

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

于 2012-01-10T18:26:44.920 回答
1

我会使用一个任务。这样整个操作在后台运行而不会阻塞 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)));
                });            
        });
    }
于 2012-01-10T18:34:16.440 回答
0

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

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 在他的回答中推荐的那样。

于 2012-01-10T18:21:17.577 回答
0

多线程和后台处理之间存在很大差异,尽管您可以同时使用它们。

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

使用多线程- 也许使用Parallel Extensions Library - 您可以使用多个线程并行运行重复循环,以便更快地完成。如果您的用户在继续之前等待该过程完成,这将是有益的。

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

于 2012-01-10T18:29:18.193 回答