使用Tasks和WaitAll递归

时间:2014-05-30 23:01:39

标签: c# recursion task-parallel-library

基本上这就是问题,我有一个函数“GetAllCandidates”接受一个列表,计算一些东西,改变参数并使用Task.Factory.StartNew调用自己来计算原始数组不同变形的所有可能输出。 / p>

    private void GetAllCandidates(List<int> values)
    {
        Interlocked.Increment(ref _count);

        for (var i = values.Count - 1; i >= 1; i --)
        {
           //Do work and calculations

           Task.Factory.StartNew(
                           () => GetAllCandidatePowers(newPowers)
           );
        }
    }

我需要充分利用CPU上的内核并等待所有任务完成,这就是问题所在。

  • 如果我为任务调用.Wait(),我的核心不会被利用(CPU使用率非常低),因此计算无法快速完成。
  • 创建任务的全局列表然后调用WaitAll不起作用,因为它是一个随机的递归调用,当我的程序到达WaitAll()的调用时,任务列表没有完全填充,并且因为计算是巨大的,当我尝试将我的任务添加到全局任务列表时,我得到一个OutOfMemoryException。

有关如何处理此类问题的任何提示?

3 个答案:

答案 0 :(得分:4)

您可以使用Task.WhenAll方法(在.NET 4.5中引入)在递归的每一步解开并行性:

private Task GetAllCandidates(List<int> values)
{
    Interlocked.Increment(ref _count);

    List<Task> tasks = new List<Task>();

    for (var i = values.Count - 1; i >= 1; i--)
    {
        //Do work and calculations

        Task task = Task.Factory.StartNew(() => GetAllCandidatePowers(newPowers));
        tasks.Add(task);
    }

    return Task.WhenAll(tasks);
}

这样,您只需等待应用程序中GetAllCandidates方法的最外层调用(如果有的话),只阻塞一个线程。

编辑:使用LINQ等效制定for循环:

private Task GetAllCandidates(List<int> values)
{
    Interlocked.Increment(ref _count);

    var tasks = Enumerable.Range(1, values.Count - 1).Reverse().Select(i =>
    {
        //Do work and calculations

        return Task.Factory.StartNew(() => GetAllCandidatePowers(newPowers));
    });

    return Task.WhenAll(tasks);
} 

答案 1 :(得分:2)

GetAllCandidates保持一个函数本地的任务列表。在函数结束时等待它们。只要您启动多个任务,这仍然可以提供并行性。

答案 2 :(得分:1)

听起来你的目标是提高整个过程的表现。假设执行for循环的顺序并不重要,可以使用TPL。我认为它会让你获得更快的结果。

private void GetAllCandidates(List<int> values)
{
    Interlocked.Increment(ref _count);

        Parallel.ForEach(test, item =>
        {
            //Do work and calculations
            var t = new Task().Run(GetAllCandidatePowers(newPowers));
            // Although if you are waiting here, you might as well just run it synchronously.
            t.Wait(); 
       );
    }
}

更新:Use PLINQ以维持订单

我提供了一个使用PLINQ并行运行操作然后以原始顺序返回结果集的示例。除非这些操作很昂贵,或者存在大量操作,否则您可能不会发现性能大幅提升。如果你正在做一些沉重的工作,或者有大量的物品要经过,那么这应该会帮助你。

private void GetAllCandidates(List<int> values)
{
    Interlocked.Increment(ref _count);

    values.AsParallel().AsOrdered().ForAll((item) => 
    {
        // Do stuff


        GetAllCandidates(newPowers);
    }
}