【问题标题】:Parallel Task and Subtasks workflow并行任务和子任务工作流程
【发布时间】:2018-03-02 12:00:46
【问题描述】:

我是 C# 线程和任务的新手,我正在尝试开发一个工作流但没有成功,可能是因为我将任务与迭代混合...

重点是:

我有一堆列表,在每个列表中都有一些事情要做,并且需要使它们尽可能多地并行工作并且尽可能减少阻塞,并且一旦每个 subBunchOfThingsTodo 完成(这意味着每一件事在里面做是并行完成的)它做了一些事情(DoSomethingAfterEveryThingToDoOfThisSubBunchOfThingsAreDone())。

例如:

bunchOfSubBunchsOfThingsTodo

  • subBunchOfThingsTodo

    • ThingToDo1
    • ThingToDo2
  • subBunchOfThingsTodo

    • ThingToDo1
    • ThingToDo2
    • ThingToDo3
  • subBunchOfThingsTodo

    • ThingToDo1
    • ThingToDo2...

这就是我正在尝试的方式,但不幸的是,每次迭代都会等待前一个 bundleOfThingsToDo,我需要它们并行工作。 要做的事情也是如此,他们等待上一件事情开始......

List<X> bunchOfSubBunchsOfThingsTodo = getBunchOfSubBunchsOfThingsTodo();     
foreach (var subBunchOfThingsToDo in bunchOfSubBunchsOfThingsTodo)
{
    int idSubBunchOfThingsToDo = subBunchOfThingsToDo.ThingsToDo.FirstOrDefault().IdSubBunchOfThingsToDo;
    
    var parent = Task.Factory.StartNew(() =>
    {
        foreach (var thingToDo in subBunchOfThingsToDo.ThingsToDo)
        {
            var child = Task.Factory.StartNew(() =>
            {
               //Do some stuff with thingToDo... Here I call several business methods
            });
        }
    });

    parent.Wait();
    DoSomethingAfterEveryThingToDoOfThisSubBunchOfThingsAreDone(idSubBunchOfThingsToDo);
}

【问题讨论】:

  • 这是 business CPU 绑定吗?
  • 如果您的“任务”彼此独立且足够大,请查看 Parallel.ForEach。
  • Use Task.Run not Task.Factory.StartNew。不是你的问题,但有一些非常具体的情况应该使用 StartNew,我猜这不是其中之一
  • Parallel.ForEach 可能是这里的路
  • @fgc 你的真正目标是什么?不要描述你尝试了什么,描述你想要什么。您想在背景中执行工作而不阻塞用户界面吗?或者进行很多远程调用,因此是异步调用?或者执行一些 CPU 繁重的工作?不同的要求,不同的等级。例如,您可以将 Parallel.ForEach(subBunchOfThingsToDo.ThingsToDo, thingToDo =&gt;WorkKnowingItsParallelAlready(thingToDo)) 用于 CPU 密集型工作

标签: c# multithreading task


【解决方案1】:

您可能想尝试使用 Task.WhenAll 并使用 linq 来生成热任务集合:

static async void ProcessThingsToDo(IEnumerable<ThingToDo> bunchOfThingsToDo)
{
    IEnumerable<Task> GetSubTasks(ThingToDo thing) 
        => thing.SubBunchOfThingsToDo.Select( async subThing => await Task.Run(subThing));

    var tasks = bunchOfThingsToDo
        .Select(async thing => await Task.WhenAll(GetSubTasks(thing)));

    await Task.WhenAll(tasks);
}

这样,您将在单独的任务上运行每个 subThingToDo,并且您只会获得一个由每个 thingToDo

的所有子任务组成的任务

编辑

ThingToDo 在这个示例中是一个相当简单的类:

class ThingToDo
{
    public IEnumerable<Action> SubBunchOfThingsToDo { get; }
}

【讨论】:

  • 你错过了当处理所有子事物时必须调用'DoSomthingAfter..'
【解决方案2】:

只需对您的代码进行最少的更改,您就可以尝试这种方式:

    var toWait = new List<Task>();
    List<X> bunchOfSubBunchsOfThingsTodo = getBunchOfSubBunchsOfThingsTodo();     

    foreach (var subBunchOfThingsToDo in bunchOfSubBunchsOfThingsTodo)
    {
        int idSubBunchOfThingsToDo = subBunchOfThingsToDo.ThingsToDo.FirstOrDefault().IdSubBunchOfThingsToDo;
    
        var parent = Task.Factory.StartNew(() =>
        {
            Parallel.ForEach(subBunchOfThingsToDo.ThingsToDo,
                thingToDo =>
                {
                        //Do some stuff with thingToDo... Here I call several business methods
                });
        });
    
        //parent.Wait();
        var handle = parent.ContinueWith((x) =>
        {
            DoSomethingAfterEveryThingToDoOfThisSubBunchOfThingsAreDone(idSubBunchOfThingsToDo);
        })
        .Start();

        toWait.Add(handle);
    }

    Task.WhenAll(toWait);
        

感谢 downvoters 团队,他们提出了“好的”解决方案:

    var bunchOfSubBunchsOfThingsTodo = getBunchOfSubBunchsOfThingsTodo();
    var toWait = bunchOfSubBunchsOfThingsTodo
        .Select(subBunchOfThingsToDo =>
        {
            return Task.Run(() =>
            {
                int idSubBunchOfThingsToDo = subBunchOfThingsToDo.ThingsToDo.FirstOrDefault().IdSubBunchOfThingsToDo;

                Parallel.ForEach(subBunchOfThingsToDo.ThingsToDo,
                    thingToDo =>
                    {
                        //Do some stuff with thingToDo... Here I call several business methods
                    });

                DoSomethingAfterEveryThingToDoOfThisSubBunchOfThingsAreDone(idSubBunchOfThingsToDo);
            });
        });

    Task.WhenAll(toWait);

【讨论】:

  • @Liam,所以问题是关于如何在子束完成但没有停止线程时处理它
  • @Liam,正如您所看到的那样,做了一个“更改最少”的描述,这不是最好的解决方案,而是一种帮助提问者了解问题所在的方法。
  • @gabba 也许你应该重新考虑一下你的代码是做什么的?你用Parallel.ForEach,即tasks来启动tasks?还不如使用一个简单的循环。这些任务会立即丢失,并且永远不会附加到“父”任务。 ` parent.ContinueWith` 或 parent.Wait() 将立即返回,因为它所做的唯一事情就是生成一些即发即弃的任务。
  • @gabba 现在也删除父级并使用 only await Task.Run(()=&gt;Parallel.ForEach(...)。去掉那个.Start(),你就不需要冷任务了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-05
  • 2011-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多