创建大量任务/线程并等待它们全部完成

本文关键字:全部 等待 任务 线程 创建 | 更新日期: 2023-09-27 17:58:35

我正在编写一个相当简单的光线跟踪器,由于程序是单线程的,我遇到了运行时限制。我在谷歌上找到的结果是,所有人都回答了这类问题,需要处理两三项任务。

class Program
{
    static void Main(string[] args)
    {
        var taskList = new List<Task>();
        taskList.Add(Task.Factory.StartNew(() => doStuff()));
        taskList.Add(Task.Factory.StartNew(() => doStuff()));
        taskList.Add(Task.Factory.StartNew(() => doStuff()));
        Task.WaitAll(taskList);
        Console.WriteLine("All threads complete");
    }
    static void doStuff()
    {
        //do stuff here
    }
}

我正在研究至少10000个单独的线程,如果是天真地实现的话。在这种情况下,上面的解决方案似乎不是最佳的。标准库中是否有一部分支持这一点,或者是否有一个Nuget包实现了类似的功能?这也可能只是我的愚蠢,列表中超过10000个线程根本不是问题。然后问题就变成了截止时间。在某些情况下,我需要12500000个任务/线程,我很确定这对于列表来说太多了。

以下是我将如何创建一个新的线程/任务的大致情况。

for (var x = 0; x < image.Width; x++) {
    for (var y = 0; y < image.Height; y++) {
        var coordinates = new Vector3(x, y, 0);
        var task = new Task(() => {
            RenderSinglePixel(coordinates);
        });
    }
}

创建大量任务/线程并等待它们全部完成

如果您有一个要使用多个线程处理的值列表(或其他IEnumerable<T>),则可以使用.AsParallel()来处理。

这会智能地限制同时派生的线程数量,具体取决于处理器的功能。但是,请注意,只有当每个项目的工作量相对较大时,才应该使用此选项。

这里有一个例子:

using System;
using System.Linq;
using System.Threading;
namespace Demo
{
    class Program
    {
        static void Main()
        {
            var numbersToProcess = Enumerable.Range(1, 1000);
            numbersToProcess.AsParallel().ForAll(doStuff);
        }
        static void doStuff(int value)
        {
            Console.WriteLine("Thread {0} is processing {1}", Thread.CurrentThread.ManagedThreadId, value);
            Thread.Sleep(250); // Simulate compute-bound task.
        }
    }
}

另一种方法是为每个方法调用创建任务,但要知道所有线程何时完成会变得更加困难,除非您存储任务以等待它们完成(但线程池的使用将确保线程数量不会变得太多):

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
    class Program
    {
        static void Main()
        {
            var numbersToProcess = Enumerable.Range(1, 1000);
            foreach (int number in numbersToProcess)
            {
                int n = number;
                Task.Run(() => doStuff(n));
            }
            Console.ReadLine();
        }
        static void doStuff(int value)
        {
            Console.WriteLine("Thread {0} is processing {1}", Thread.CurrentThread.ManagedThreadId, value);
            Thread.Sleep(250); // Simulate compute-bound task.
        }
    }
}

注意如果每次调用doStuff()都需要很长时间,那么这种方法确实存在创建线程数量失控的风险。如果将Thread.Sleep(250)更改为Thread.Sleep(100000)并运行程序,您将看到创建了大量线程。

但您最好的选择可能是使用DataFlow TPL。

对小型实体模式使用并行循环。https://msdn.microsoft.com/en-us/library/dd560853(v=vs.110).aspx

当Parallel.For循环的主体很小时,它的执行速度可能比等效的顺序循环慢,例如C#中的For循环和Visual Basic中的For循环。较慢的性能是由划分数据所涉及的开销以及在每次循环迭代中调用委托的成本造成的。为了解决这种情况,Partitioner类提供了Partitioner.Create方法,该方法使您能够为委托主体提供一个顺序循环,这样每个分区只调用一次委托,而不是每次迭代调用一次。

"小实体并行循环"模式本质上是对可枚举对象进行分区,并根据处理器的数量在多个线程中执行循环。每个线程都有自己的分区组来处理

在这种情况下,这种模式比普通的并行循环更好(更具性能),因为它避免了创建超过必要数量的线程的开销。

使用多于CPU核心的线程只会降低处理的总体速度