任务开始顺序而不是并行

时间:2016-07-14 09:22:16

标签: c# parallel-processing async-await task-parallel-library

我需要并行启动任务,但我选择使用Task.Run而不是Parallel.Foreach,因此我可以在所有任务完成后获得一些反馈并启用UI控件。

private async void buttonStart_Click(object sender, EventArgs e)
{
    var cells = objectListView.CheckedObjects;
    if(cells != null)
    {
        List<Task> tasks = new List<Task>();
        foreach (Cell c in cells)
        {
            Cell cell = c;
            var progressHandler = new Progress<string>(value =>
            {
                cell.Status = value;
            });
            var progress = progressHandler as IProgress<string>;

            Task t = Task.Run(() =>
            {
                progress.Report("Starting...");
                int a = 123;
                for (int i = 0; i < 200000; i++)
                {
                    a = a + i;
                    Task.Delay(500).Wait();
                }

                progress.Report("Done");
            });
            tasks.Add(t);

        }

        await Task.WhenAll(tasks);
        Console.WriteLine("Done, enabld UI controls");
    }
}

所以我期待的是,我在UI“开始...”中几乎可以立即看到所有项目。我实际看到的是前4个项目是“正在开始......”(我猜因为每个线程都使用了所有4个CPU内核),然后每秒或更少的新项目是“正在启动”。我总共有37个项目,所有项目启动所有任务大约需要30秒。

如何让它尽可能平行?

3 个答案:

答案 0 :(得分:2)

  

如何让它尽可能平行?

     

内部for循环的一部分是模拟长时间运行的CPU绑定作业,我想尽可能同时开始。

它已尽可能平行。启动所有具有CPU限制工作的37个线程不会让它更快,因为你显然是在4核机器上运行它。有4个核心,因此一次只能运行4个线程。其他33个线程将等待4个正在运行。他们只会出现同时运行。

也就是说,如果你真的希望启动所有这些线程池线程,你可以通过调用ThreadPool.SetMinThreads来实现这一点。

  

我需要并行启动任务,但我选择使用Task.Run而不是Parallel.Foreach,所以我可以在所有任务完成后获得一些反馈并启用UI控件。

由于您要完成并行工作,因此应使用Parallel。如果你想要await的漂亮的UI-thread-thread行为,那么你可以使用一个await Task.Run,如下所示:

private async void buttonStart_Click(object sender, EventArgs e)
{
  var cells = objectListView.CheckedObjects;
  if (cells == null)
    return;

  var workItems = cells.Select(c => new
  {
    Cell = c,
    Progress = new Progress<string>(value => { c.Status = value; }),
  }).ToList();

  await Task.Run(() => Parallel.ForEach(workItems, item =>
  {
    var progress = item.Progress as IProgress<string>();
    progress.Report("Starting...");
    int a = 123;
    for (int i = 0; i < 200000; i++)
    {
      a = a + i;
      Thread.Sleep(500);
    }

    progress.Report("Done");
  }));

  Console.WriteLine("Done, enabld UI controls");
}

答案 1 :(得分:0)

我说,它尽可能平行。如果你有4个核心,你可以并行运行4个线程。

如果你可以在等待&#34;延迟&#34;时做一些事情,那么看一下异步编程(其中一个线程可以运行多个任务&#34;一次&#34;,因为大多数是等待什么)。

编辑:您还可以在自己的任务中运行Parallel.ForEach,并await

private async void buttonStart_Click(object sender, EventArgs e)
{
    var cells = objectListView.CheckedObjects;
    if(cells != null)
    {
        await Task.Run( () => Parallel.ForEach( cells, c => ... ) );
    }
}

答案 2 :(得分:0)

我认为它依赖于你的taskcreation-options。

TaskCreationOptions.LongRunning

在这里您可以找到更多信息:

https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcreationoptions(v=vs.110).aspx

但是你必须知道,该任务使用的线程池具有有限的最大线程数。您可以使用 LongRunning 发出信号,表示此任务需要很长时间,并且不应阻塞您的池。我认为创建一个长时间运行的任务会更复杂,因为调度程序可能会创建一个新线程。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace TaskTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var demo = new Program();
            demo.SimulateClick();
            Console.ReadLine();
        }

        public void SimulateClick()
        {
            buttonStart_Click(null, null);
        }

        private async void buttonStart_Click(object sender, EventArgs e)
        {
            var tasks = new List<Task>();
            for (var i = 0; i < 36; i++)
            {
                var taskId = i;
                var t = Task.Factory.StartNew((() =>
                {
                    Console.WriteLine($"Starting Task ({taskId})");
                    for (var ii = 0; ii < 200000; ii++)
                    {
                        Task.Delay(TimeSpan.FromMilliseconds(500)).Wait();
                        var s1 = new string(' ', taskId);
                        var s2 = new string(' ', 36-taskId);
                        Console.WriteLine($"Updating Task {s1}X{s2} ({taskId})");
                    }
                    Console.Write($"Done ({taskId})");
                }),TaskCreationOptions.LongRunning);
                tasks.Add(t);
            }
            await Task.WhenAll(tasks);
            Console.WriteLine("Done, enabld UI controls");
        }
    }
}