前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C#中如何使用Parallel.For和Parallel.ForEach

C#中如何使用Parallel.For和Parallel.ForEach

作者头像
苏州程序大白
发布2021-08-13 13:24:17
5.8K0
发布2021-08-13 13:24:17
举报
文章被收录于专栏:用户8907256的专栏

C#中如何使用Parallel.For和Parallel.ForEach

利用C#中的无锁,线程安全的实现来最大化.NET或.NET Core应用程序的吞吐量。

在这里插入图片描述
在这里插入图片描述

并行是在具有多个内核的系统上并行执行任务的能力。.NET Framework 4中引入了对.NET中并行编程的支持。.NET中的并行编程使我们能够更有效地使用系统资源,并具有更好的编程控制能力。本文讨论了如何在.NET Core应用程序中使用并行性。若要使用本文提供的代码示例,您应该在系统中安装Visual Studio 2019。 在Visual Studio中创建一个.NET Core控制台应用程序项目 首先,让我们在Visual Studio中创建一个.NET Core控制台应用程序项目。假设系统中已安装Visual Studio 2019,请按照以下概述的步骤在Visual Studio中创建一个新的.NET Core控制台应用程序项目。

1、启动Visual Studio IDE。 2、点击“创建新项目”。 3、在“创建新项目”窗口中,从显示的模板列表中选择“控制台应用程序(.NET Core)”。 4、点击下一步。 5、在“配置新项目”窗口中,指定新项目的名称和位置。 6、单击创建。 在本文的后续部分中,我们将使用该项目来说明.NET Core中的并行编程。 .NET Core中的并发性和并行性 并发和并行性是.NET和.NET Core中的两个关键概念。尽管它们看起来相同,但是它们之间还是存在细微的差异。 考虑必须由应用程序执行的两个任务T1和T2。如果一项处于执行状态而另一项正在等待执行,则这两项任务处于并发执行状态。结果,一项任务先于另一项完成。相反,如果两个任务同时执行,则两个任务并行执行。为了实现任务并行性,程序必须在具有多个内核的CPU上运行。 .NET Core中的Parallel.For和Parallel.ForEach Parallel.For循环执行可能并行运行的迭代。您可以监视甚至操纵循环的状态。Parallel.For循环类似于for循环,不同之处在于它允许迭代在多个线程中并行运行。 Parallel.ForEach方法将要完成的工作分成多个任务,每个任务用于集合中的每个项目。Parallel.ForEach类似于C#中的foreach循环,除了foreach循环在单个线程上运行并且处理顺序进行,而Parallel.ForEach循环在多个线程上运行并且处理以并行方式进行。 C#中的Parallel.ForEach与foreach 考虑以下方法,该方法接受整数作为参数,如果它是质数,则返回true。

代码语言:javascript
复制
    static bool IsPrime(int integer)
        {
            if (integer <= 1) return false;
            if (integer == 2) return true;
            var limit = Math.Ceiling(Math.Sqrt(integer));
            for (int i = 2; i <= limit; ++i)
                if (integer % i == 0)
                    return false;
            return true;
        }

现在,我们将利用ConcurrentDictionary存储素数和托管线程ID。由于两个范围之间的质数是唯一的,因此我们可以将它们用作键,并将托管线程ID用作值。 .NET中的并发集合包含在System.Collections.Concurrent命名空间内,并提供了该集合类的无锁和线程安全实现。ConcurrentDictionary类包含在System.Collections.Concurrent命名空间内,并表示一个线程安全的字典。 以下两种方法都使用IsPrime方法检查整数是否为质数,将质数和托管线程ID存储在ConcurrentDictionary的实例中,然后返回该实例。第一种方法使用并发,第二种方法使用并行性。

代码语言:javascript
复制
  private static ConcurrentDictionary<int, int>
        GetPrimeNumbersConcurrent(IList<int> numbers)
        {
            var primes = new ConcurrentDictionary<int, int>();
            foreach (var number in numbers)
            {               
                if(IsPrime(number))
                {
                    primes.TryAdd(number,
                    Thread.CurrentThread.ManagedThreadId);
                }
            }
            return primes;
        }
        private static ConcurrentDictionary<int, int>
        GetPrimeNumbersParallel(IList<int> numbers)
        {
            var primes = new ConcurrentDictionary<int, int>();
            Parallel.ForEach(numbers, number =>
            {
                if (IsPrime(number))
                {
                    primes.TryAdd(number,
                    Thread.CurrentThread.ManagedThreadId);
                }
            });
            return primes;
        }

C#中的并发与并行示例 下面的代码段说明了如何调用GetPrimeNumbersConcurrent方法来检索1到100之间的所有素数以及托管线程ID。

代码语言:javascript
复制
static void Main(string[] args)
        {
            var numbers = Enumerable.Range(0, 100).ToList();
            var result = GetPrimeNumbersConcurrent(numbers);
            foreach(var number in result)
            {
                Console.WriteLine($"Prime Number:
                {string.Format("{0:0000}",number.Key)},
                Managed Thread Id: {number.Value}");
            }
            Console.Read();
        }

执行上面的程序时,应该看到如图所示的输出:

在这里插入图片描述
在这里插入图片描述

如您所见,托管线程ID在每种情况下都是相同的,因为在此示例中我们使用了并发性。现在,让我们看一下使用线程并行性时的输出结果。以下代码段说明了如何使用并行性检索介于1到100之间的质数。

代码语言:javascript
复制
static void Main(string[] args)
        {
            var numbers = Enumerable.Range(0, 100).ToList();
            var result = GetPrimeNumbersParallel(numbers);
            foreach(var number in result)
            {
                Console.WriteLine($"Prime Number:
                {string.Format("{0:0000}",number.Key)},
                Managed Thread Id: {number.Value}");
            }
            Console.Read();
        }

当执行上述程序时,输出应类似于图所示:

在这里插入图片描述
在这里插入图片描述

如您所见,因为我们使用了Parallel.ForEach,所以已经创建了多个线程,因此托管线程ID是不同的。 限制C#中的并行度 并行度是一个无符号整数,表示查询在执行过程中应利用的最大处理器数量。换句话说,并行度是一个整数,表示将在同一时间点执行以处理查询的最大任务数。

默认情况下,Parallel.For和Parallel.ForEach方法对衍生任务的数量没有限制。因此,在上面显示的G​​etPrimeNumbersParallel方法中,程序尝试使用系统中的所有可用线程。

您可以利用MaxDegreeOfParallelism属性来限制生成任务的数量(每个ParallelOptions实例的Parallel类)。如果MaxDegreeOfParallelism设置为-1,则并发运行的任务数没有限制。

以下代码段显示了如何设置MaxDegreeOfParallelism以使用最多75%的系统资源。

代码语言:javascript
复制
new ParallelOptions
{
    MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 2.0))
};

请注意,在上面的代码段中,我们将处理器数量乘以2,因为每个处理器包含两个内核。这是GetPrimeNumbersParallel方法的完整更新代码,供您参考:

代码语言:javascript
复制
 private static ConcurrentDictionary<int, int> GetPrimeNumbersParallel(IList<int> numbers)
        {
            var primes = new ConcurrentDictionary<int, int>();
            Parallel.ForEach(numbers, number =>
            {
                new ParallelOptions
                {
                    MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 2.0))
                };
                if (IsPrime(number))
                {
                    primes.TryAdd(number,
                    Thread.CurrentThread.ManagedThreadId);
                }
            });
            return primes;
        }

确定并行循环是否在C#中完成 请注意,Parallel.For和Parallel.ForEach均返回ParallelLoopResult的实例,该实例可用于确定并行循环是否已完成执行。以下代码片段显示了如何使用ParallelLoopResult。

代码语言:javascript
复制
ParallelLoopResult parallelLoopResult = Parallel.ForEach(numbers, number =>
 {
    new ParallelOptions
    {
          MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling(
          (Environment.ProcessorCount * 0.75) * 2.0))
    };
    if (IsPrime(number))
    {
          primes.TryAdd(number, Thread.CurrentThread.ManagedThreadId);
    }
 });
Console.WriteLine("IsCompleted: {0}", parallelLoopResult.IsCompleted);

要在非泛型集合中使用Parallel.ForEach,您应该利用Enumerable.Cast扩展方法将集合转换为泛型集合,如下面的代码片段所示:

代码语言:javascript
复制
Parallel.ForEach(nonGenericCollection.Cast<object>(),
    currentElement =>
    {
    });

最后一点,不要假设Parallel.For或Parallel.ForEach的迭代将始终并行执行。你还应该注意线程相似性问题。你可以阅读有关任务并行微软的在线文档中的这些和其他潜在的陷阱在这里。

关注苏州程序大白,持续更新技术分享。谢谢大家支持

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/04/19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C#中如何使用Parallel.For和Parallel.ForEach
  • 关注苏州程序大白,持续更新技术分享。谢谢大家支持
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档