Parallel.ForEach最后执行一个任务

时间:2017-10-09 20:24:25

标签: c# multithreading parallel-processing

我有一个要使用Parallel.ForEach并行执行的任务列表。它可以通过并行运行的4个任务开始,但最终它一次只减少到一个任务。 以下是并行任务的计数:

1 2 3 4 4 3 4 4 ... 4 4 4 3 3 1 1 1 1 1 1

最大并行度设置为4.在执行结束时,一次只执行一个任务,并且所有执行都在同一个线程上运行。我的问题是为什么我最终会一次执行这一项任务?我怎么能避免这个?

以下是代码:

var threadCount = 4;
ThreadPool.SetMinThreads(threadCount, threadCount);
Parallel.ForEach(taskDataList, 
    new ParallelOptions() {MaxDegreeOfParallelism = threadCount},
    (x) => { RunOne(x); });

RunOne函数启动外部进程并等待它结束。有些人怀疑RunOne可能是缺乏并行执行的问题。为了确保不是这种情况,我通过将此函数替换为具有相同持续时间的睡眠调用来重新创建情境。 代码如下。这里 t 是每个任务所需的秒数列表。 activeCount 是当前正在运行的任务的数量,剩余是仍保留在列表中的任务数。

var t = new List<int>()   
{2,2,2,1,1,1,1,1,1,1,
 1,1,1,1,1,3,1,1,1,1,
 1,1,1,1,1,1,1,1,5,4,
 26,12,11,16,44,4,37,26,13,36};
int activeCount = 0;
int remaining = t.Count;
Parallel.ForEach(t, new ParallelOptions() {MaxDegreeOfParallelism = 4},
    (x) =>
    {
        Console.WriteLine($"Active={Interlocked.Increment(ref activeCount)}"+
            $"Remaining={Interlocked.Decrement(ref remaining)} " +
            $"Run thread={Thread.CurrentThread.ManagedThreadId}");
        Thread.Sleep(x * 1000); //Sleep x seconds
        Interlocked.Decrement(ref activeCount);
    });

最后它产生如下输出:

Active=2 Remaining=7 Run thread=3
Active=1 Remaining=6 Run thread=3
Active=1 Remaining=5 Run thread=3
Active=1 Remaining=4 Run thread=3
Active=1 Remaining=3 Run thread=3
Active=1 Remaining=2 Run thread=3
Active=1 Remaining=1 Run thread=3
Active=1 Remaining=0 Run thread=3

此输出显示最终只有1个任务正在运行,当时仍有6个任务。限制4个并行任务没有任何意义。当6个任务仍然可用时,我希望看到4个任务并行运行。

我应该以不同方式使用Parallel.ForEach还是一个错误/功能?

1 个答案:

答案 0 :(得分:1)

在查看Parallel.ForEach的参考源之后,我发现不是将元素逐个分发到不同的线程,而是将任务列表分成块,然后将任务列表提供给每个线程。对于长期运行的任务来说,这是非常低效的方法

        var t = new List<int>()
            {2,2,2,1,1,1,1,1,1,1,
             1,1,1,1,1,3,1,1,1,1,
             1,1,1,1,1,1,1,1,5,4,
             26,12,11,16,44,4,37,26,13,36};
        int activeCount = 0;
        int remaining = t.Count;
        var cq = new ConcurrentQueue<int>(t);
        var tasks = new List<Task>();
        for (int i = 0; i < 4; i++) tasks.Add(Task.Factory.StartNew(() => 
        {
            int x;
            while (cq.TryDequeue(out x))
            {
                Console.WriteLine($"Active={Interlocked.Increment(ref activeCount)} " +
                    $"Remaining={Interlocked.Decrement(ref remaining)} " +
                    $"Run thread={Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(x * 1000); //Sleep x seconds
                Interlocked.Decrement(ref activeCount);
            }
        }));
        Task.WaitAll(tasks.ToArray());

我使用了第一个代码示例中的4个并行任务。使用Parallel时,这种情况下的执行时间为83秒.ForEach花了211秒。这证明了Parallel.ForEach在某些情况下效率非常低,应该谨慎使用。