任务结合结果并继续

时间:2020-10-08 15:22:49

标签: c# .net parallel-processing task

我有16个任务做同样的工作,每个任务都返回一个数组。我想成对地合并结果并做同样的工作,直到只有一项任务为止。我不知道什么是最好的方法。

public static IComparatorNetwork[] Prune(IComparatorNetwork[] nets, int numTasks)
    {
        var tasks = new Task[numTasks];
        var netsPerTask = nets.Length/numTasks;
        var start = 0;
        var concurrentSet = new ConcurrentBag<IComparatorNetwork>();
        
        for(var i = 0; i  < numTasks; i++)
        {
            IComparatorNetwork[] taskNets;
            if (i == numTasks - 1)
            {
                taskNets = nets.Skip(start).ToArray();                 
            }
            else
            {
                taskNets = nets.Skip(start).Take(netsPerTask).ToArray();
            }

            start += netsPerTask;
            tasks[i] = Task.Factory.StartNew(() =>
            {
                var pruner = new Pruner();
                concurrentSet.AddRange(pruner.Prune(taskNets));
            });
        }

        Task.WaitAll(tasks.ToArray());

        if(numTasks > 1)
        {
            return Prune(concurrentSet.ToArray(), numTasks/2);
        }

        return concurrentSet.ToArray();
    }

现在我正在等待所有任务完成,然后重复执行一半的任务,直到只有一个。我不想在每次迭代中都等待所有的时间。我对并行编程非常陌生,这种方法可能不好。 我尝试并行化的代码如下:

public IComparatorNetwork[] Prune(IComparatorNetwork[] nets)
    {
        var result = new List<IComparatorNetwork>();

        for (var i = 0; i < nets.Length; i++) 
        {
            var isSubsumed = false;

            for (var index = result.Count - 1; index >= 0; index--)
            {
                var n = result[index];

                if (nets[i].IsSubsumed(n))
                {
                    isSubsumed = true;
                    break;
                }

                if (n.IsSubsumed(nets[i]))
                {
                    result.Remove(n);
                }
            }

            if (!isSubsumed) 
            {
                result.Add(nets[i]);
            }
        }

        return result.ToArray();
    }`

1 个答案:

答案 0 :(得分:2)

因此,您在这里所做的基本工作是汇总值,但同时进行。幸运的是,PLINQ已经具有可并行工作的Aggregate实现。因此,在您的情况下,您可以将原始数组中的每个元素简单地包装在其自己的一个元素数组中,然后您的Prune操作就可以将任意两个网络数组组合成一个新的单个数组。

public static IComparatorNetwork[] Prune(IComparatorNetwork[] nets)
{
    return nets.Select(net => new[] { net })
        .AsParallel()
        .Aggregate((a, b) => new Pruner().Prune(a.Concat(b).ToArray()));
}

我对它们的聚合方法的内部知识不是很了解,但是我想它可能很好,并且不需要花费很多时间不必要地等待。但是,如果您想编写自己的东西,以便可以确保工人总是在开始新工作时就立即进行新工作,这就是我自己的实现。可以根据您的特定情况随意比较两者,以了解哪种最适合您的需求。请注意,PLINQ可以通过多种方式进行配置,请随时尝试其他配置以查看哪种方式最适合您的情况。

public static T AggregateInParallel<T>(this IEnumerable<T> values, Func<T, T, T> function, int numTasks)
{
    Queue<T> queue = new Queue<T>();
    foreach (var value in values)
        queue.Enqueue(value);
    if (!queue.Any())
        return default(T);  //Consider throwing or doing something else here if the sequence is empty

    (T, T)? GetFromQueue()
    {
        lock (queue)
        {
            if (queue.Count >= 2)
            {
                return (queue.Dequeue(), queue.Dequeue());
            }
            else
            {
                return null;
            }
        }
    }

    var tasks = Enumerable.Range(0, numTasks)
        .Select(_ => Task.Run(() =>
        {
            var pair = GetFromQueue();
            while (pair != null)
            {
                var result = function(pair.Value.Item1, pair.Value.Item2);
                lock (queue)
                {
                    queue.Enqueue(result);
                }
                pair = GetFromQueue();
            }
        }))
        .ToArray();
    Task.WaitAll(tasks);
    return queue.Dequeue();
}

此版本的调用代码如下:

public static IComparatorNetwork[] Prune2(IComparatorNetwork[] nets)
{
    return nets.Select(net => new[] { net })
        .AggregateInParallel((a, b) => new Pruner().Prune(a.Concat(b).ToArray()), nets.Length / 2);
}

如评论中所述,可以通过使修剪器的Prune方法接受两个集合,而不仅仅是一个集合,并且仅将每个集合中的项目与另一个集合进行比较,而知道修剪器中的所有项目,从而使修剪器的public static IReadOnlyList<IComparatorNetwork> Prune(IReadOnlyList<IComparatorNetwork> first, IReadOnlyList<IComparatorNetwork> second) { var firstItemsNotSubsumed = first.Where(outerNet => !second.Any(innerNet => outerNet.IsSubsumed(innerNet))); var secondItemsNotSubsumed = second.Where(outerNet => !first.Any(innerNet => outerNet.IsSubsumed(innerNet))); return firstItemsNotSubsumed.Concat(secondItemsNotSubsumed).ToList(); } 方法更加高效。同一集合不会包含该集合中的任何其他集合。这不仅使该方法更短,更简单,更易于理解,而且消除了昂贵比较中的很大一部分。少量修改也可以大大减少创建的中间集合的数量。

public static IReadOnlyList<IComparatorNetwork> Prune(IReadOnlyList<IComparatorNetwork> nets)
{
    return nets.Select(net => (IReadOnlyList<IComparatorNetwork>)new[] { net })
        .AggregateInParallel((a, b) => Pruner.Prune(a, b), nets.Count / 2);
}

通过调用代码,只需进行少量修改即可确保类型匹配,并且您可以传入两个集合,而不是先对它们进行容忍。

<?xml version="1.0" encoding="UTF-8"?>
<index>
<h1>Tax consequences of abandoning trade secret, 15.44&#x2013;15.45</h1>
<h2>Licensing agreement, address provision in, 8.34&#x0192;</h2>
<h3>Secretary of State&#x2019;s office, 9.13</h3>
<h2>Punitive and compensatory damages under federal constitutional maximum, 1.5-to-one ratio between, 12.22</h2>
<h4>Secretary of State&#x2019;s office, 19.13</h4>
<h5>Resolving ambiguities in insurance policy against insurer, 14.3</h5>
<h6>Bad faith lawsuit against competitor, antitrust consequences of, 11.2, 11.81</h6>
<h2>Consent to assignment, 8.43A&#x0192;, 9.10</h2>
<h2>Crime or fraud in misappropriation of trade secret, waiver of attorney-client privilege in cases of, 11.101</h2>
<h3>Representing clients in same field of technology, 17.10A</h3>
<h2>CCP &#x00a7;425.16, anti-SLAPP motions under, 11.29</h2>
<h3>CCP &#x00a7;2019.210, 11.44, 11.51, 11.53</h3>                    
</index>