假设我有这个代码。

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
有帮助吗?

解决方案

保证订单的一种简单方法是在同一背景线程中顺序执行所有时间额定任务。

这可能比在多个线程上执行多个时间额定任务的时间稍长,但是为您节省了很多头痛,试图同步结果并按照所需的顺序产生输出。

如果您的主要目标是释放UI,以便用户可以继续进行其他工作,那么使用一个线程和多个线程之间的完成时间差可能可以忽略不计,因此请使用最简单的路由 - 为所有的所有背景线程使用单个背景线程时间额定任务。

如果您的主要目标是更快地完成时间额定任务的计算,那么运行多个线程可能是更好的方法,因为它更有可能利用多个核心并并行执行某些工作。

要保留多个线程案例中的订单,您必须自己订购结果。您可以维护累加器温度列表,并在结果中的适当位置(如果易于计算或已知的位置 /索引)中插入每个结果,或者在所有线程完成后添加一个附加处理步骤以所需顺序为最终输出的多个结果。在并行和分布的计算圆圈中,将问题分成多个块的过程要并行完成,然后将结果组合到连贯的顺序中称为“降低地图”。

其他提示

在WPF,Winforms,Silverlight等GUI应用程序中,...您可以使用 背景工作者 因为它会考虑到主线程上发生的不同回调,这在更新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();

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

我会使用一个任务。这样,整个操作在背景中运行而不会阻止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)));
                });            
        });
    }

您必须等待结果,然后才能更新UI,因此仅将QunseCumingTask()移动到背景线程没有任何感觉。您将需要从背景线程更新UI(但是将调用拨打到UI线程)。

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

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

使用背景工作者也很容易,但不像“简单”,因为还有更多代码:

  • 创建一个背景工作者
  • 分配Dowork处理程序
  • 分配进度处理程序
  • 设置WorkerReportsprogress
  • 致电runworkerAsync()

如果订单没关系,您可以使用 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,我建议您使用 背景工作者 正如达林·迪米特罗夫(Darin Dimitrov)在答案中推荐的那样。

多线程和背景处理之间存在很大的区别,尽管您可以同时使用它们。

背景处理 - 按照Darin的建议使用背景工作者 - 您可以允许用户继续在UI中工作,而无需等待背景过程完成。如果用户必须等待处理才能完成之前,则在后台运行它是没有意义的。

多线程 - 使用 并行扩展库, ,也许 - 您可以使用多个线程并行运行重复循环,从而更快地完成。如果您的用户正在等待该过程完成之前,这将是有益的。

最后,如果您在后台运行某些内容,则可以通过使用多线程使其更快地完成。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top