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

C# 创建大量任务/线程并等待它们全部完成,c#,multithreading,task,C#,Multithreading,Task,我正在编写一个相当简单的光线跟踪器,由于程序是单线程的,所以我遇到了运行时限制。我通过谷歌找到的结果都回答了这类问题,有两三个任务要处理 class Program { static void Main(string[] args) { var taskList = new List<Task>(); taskList.Add(Task.Factory.StartNew(() => doStuff())); ta

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

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
    }
}
类程序
{
静态void Main(字符串[]参数)
{
var taskList=新列表();
taskList.Add(Task.Factory.StartNew(()=>doStuff());
taskList.Add(Task.Factory.StartNew(()=>doStuff());
taskList.Add(Task.Factory.StartNew(()=>doStuff());
Task.WaitAll(任务列表);
Console.WriteLine(“所有线程完成”);
}
静态空隙度
{
//在这里做事
}
}
我正在研究至少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);
        });
    }
}
for(var x=0;x{
渲染单像素(坐标);
});
}
}
如果您有一个要使用多线程处理的值列表(或其他
IEnumerable
),可以使用
.aspallel()
来执行此操作

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

下面是一个例子:

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.
        }
    }
}
另一种方法是为每个方法调用创建任务,但如果不存储任务以等待它们完成,则更难知道所有线程何时完成(但线程池的使用将确保线程数量不会太多):

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


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

使用平行循环的小物体模式。

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

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

在这种情况下,这种模式比普通的并行循环更好(性能更好),因为它避免了创建多余线程的开销


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

一般来说,在计算范围内的任务中使用线程多于处理器核是不好的。我也是这么想的。处理我的情况的正确方法是什么?我将尝试添加一个示例。创建
任务
并不意味着创建线程。只有使用
LongRunning
标志创建的任务才真正为它们创建专用线程。所有其他任务都将排队,然后在已经创建的线程(如UI线程)或deault线程池中的任务上运行。@MatthewWatson:a
任务
不是线程。只链接的答案不受欢迎,如果链接消失,答案将不再有任何值。请将答案中链接的相关信息包含在引号块中。@Devlin小型实体并行循环模式基本上划分了可枚举对象,并根据处理器数量在多个线程中执行循环,每个线程都有自己的分区组。@MarkMenchavez-那么为什么不在回答中解释,而不是在注释中解释呢?就目前而言,你的回答质量很差。如果您解释了它是如何解决OP问题的,那么它可能会得到改进,并且,更好的是,展示一个它是如何工作的示例。答案已编辑。谢谢你们的评论和建议。我想我可以创建一个坐标数组来进行处理,但这看起来效率太低了。有没有像线程池一样的方法可以做到这一点呢?@Herbstein我的第二个例子中,你可以使用
任务
,但正如我指出的,知道最后一个任务何时完成更难。但是听起来你可能想研究数据流TPL——这有一个很大的学习曲线,但它可能最适合你想要的。谢谢你的帮助!我现在可能会实现一个只包含
任务的版本,然后在有更多时间的时候研究数据流TPL。再次感谢。我推荐下面描述的小物体平行循环模式,因为OP有很多像素需要渲染,而不是像这个答案所建议的那样进行简单的平行循环。
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.
        }
    }
}