【.NET Core】深入理解任务并行库 (TPL)

一、概述

任务并行库(TPL)英文:Task Parallel LibrarySystem.ThreadingSystem.Threading.Tasks空间中的一组公共类型和API。TPL的目的是通过简化将并行和并发添加到应用程序的过程来提高开发人员的工作效率。TPL动态缩放并发的程度以最有效地使用所有可用的处理器。此外,TPL还处理工作分区,ThreadPool上的线程调度、取消支持、状态管理以及其他低级别的细节操作。通过使用TPL,你可以在将精力集中于程序要完成的工作,同时最大程度地提高代码的性能。

在.NET Framework4中,首选TPL编写多线程代码和并行代码。但是,并不是所有代码都适合并行化。 例如,如果某个循环在每次迭代时只执行少量工作,或它在很多次迭代时都不运行,那么并行化的开销可能导致代码运行更慢。

二、数据并行(任务并行库)

数据并行指的是对源集合或数组的元素同时(既:并发)执行相同操作的场景。在数据并行操作中,对源集合进行分区,以便对个线程能够同时在不同的网段上操作。

任务并行库(TPL)支持通过System.Threading.Tasks.Parallel类实现的数据并行。此类对for循环和foreach循环提供了基于方法的并行执行。Parallel.ForParallel.ForEach循环编写的循环逻辑与编写循环的相似。无需创建线程或列工作项。在基本循环中,不需要加锁。TPL为你处理所有低级别的工作。

下面的代码示例演示了一个简单的 foreach 循环及其并行等效项。

//Sequential version
foreach(var item in sourceCollection)
{
    SetMethod(item);
}
// Parallel equivalent
Parallel.ForEach(sourceCollection, item => SetMethod(item));

并行循环运行时,TPL 将数据源进行分区,以便该循环可以同时对多个部分进行作用。 在后台,任务计划程序基于系统资源和工作负荷来划分任务。 如有可能,如果工作负荷变得不平衡了,计划程序将重新分配多个线程与处理器之间的工作。

三、Parallel.For 循环示例

static void Main(string[] args)
{
       Stopwatch stopwatch = new Stopwatch();
       stopwatch.Start();
       char[] charList = "在验证目录存在后它需要将单个目录路径作为参数,并报告该目录中文件的数量和总大小。".ToCharArray();
       Parallel.For(0, charList.Length,index =>{
                             Console.WriteLine($"index{index}");
                             Console.WriteLine($"CharList->Char:{charList[index]}");
       });
       stopwatch.Stop();
       Console.WriteLine($"Run Time  {stopwatch.ElapsedMilliseconds}");
       stopwatch.Start();
       foreach ( char c in charList )
       {
            Console.WriteLine($"CharList->Char:{c}");

       }
       stopwatch.Stop();
       Console.WriteLine($"Run Time  {stopwatch.ElapsedMilliseconds}");
       Console.WriteLine("Directory Run End");
       Console.ReadKey();
}

For的此重载的第三个参数的类型为Action<int>。不管Action委托具有零个、一个或十六个类型参数,它都始终返回void

四、Parallel.ForEach 循环示例

static void Main(string[] args)
{
     var limit = 200;
     var numbers = Enumerable.Range(0, limit).ToList();
     Parallel.ForEach(numbers, number =>
     {
         Console.WriteLine(number);
     });
     Console.ReadKey();
}

Parallel.ForEach循环的工作原理类似Parallel.For循环。该循环对源集合进行分区,并根据系统环境在多个线程上安排工作。系统上的处理器越多,并行方法的运行速度就越快。对于一些源集合,有序循环可能会更快,具体根据源大小以及该循环要执行的工作类型而定。

五、处理并行循环中的异常

Parallel.ForParallel.ForEach重载没有任何用于处理可能引发异常的特性机制。在这一方面,他们类似于常规forforeach循环;未处理的异常会导致循环在当前运行的迭代完成后立即终止。

向并行循环添加自己的异常处理逻辑时,将处理类似于在多个线程上同时引发相似异常的情况,以及一个线程上引发异常导致另一个线程上引发另一个异常的情况。你可以通过将循环中的所有异常包装到一个System.AggregateException中处理这两种情况。

static void Main(string[] args)
{
     byte[] data = new byte[20];
     Random r = Random.Shared;
     r.NextBytes(data);
     var exceptions = new ConcurrentQueue<Exception>();
     Parallel.ForEach(data, d =>
     {
        try
        {
            if (d < 3) throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3.");
            else Console.Write(d + " ");
        }
        catch (Exception e)
        {
            exceptions.Enqueue(e);
        }
      });
      Console.WriteLine();
      if (!exceptions.IsEmpty)
      {
        throw new AggregateException(exceptions);
      }
   }
}

六、数据并行总结

在许多情况下,与普通的顺序循环相比,Parallel.ForParallel.ForEach可以显著提升性能。但是,对循环进行并行的工作增加了复杂性,可能会导致在顺序代码中出现不常见或根本不会遇到的问题。

6.1 不要假定并行的速度始终更快

在某些情况下,并行循环可能比它等效的顺序循环的运行速度更慢。 基本的经验法则是具有较少迭代和快速用户委托的并行循环未必会快很多。 但是,由于性能会涉及到很多因素,因此我们建议始终衡量实际的结果。

6.2 不要假定并行的速度始终更快

在顺序代码中,从静态变量或类字段中读取或写入静态变量或类字段的情况很常见。 但是,每当多个线程同时访问此类变量时,则很有可能会出现争用条件。 即使可以使用锁来同步对变量的访问,但同步开销可能会对性能造成损害。 因此,我们建议尽可能地避免在一个并行循环中访问共享状态,或至少限制对共享状态的访问。

6.3 避免过度并行化

通过使用并行循环,将会产生对源集合进行分区和同步工作线程的开销成本。 计算机上的处理器数量进一步限制了并行化的优点。 仅在一个处理器上运行多个受计算限制的线程时,速度并不会得到提升。 因此,必须要小心,不要对循环进行过度并行化。

在嵌套的循环中,最有可能发生过度并行化的情况。 在大多数情况下,除非满足以下一个或多个条件,否则最好仅对外部循环进行并行化:

  • 已知内部循环非常长。
  • 正在对每个订单执行开销极大的计算。
  • 已知目标系统具有足够的处理器来处理通过对 cust.Orders 上的查询进行并行化所产生的线程数。

6.4 避免调用非线程安全方法

如果从并行循环中写入非线程安全实例方法,可能会导致出现程序可能检测到也可能检测不到的数据损坏。 还可能会导致异常。

6.5 避免调用非线程安全方法

.NET 中的大多数静态方法是线程安全的,并且可以同时从多个线程中调用。 但是,即使在这些情况下,所涉及到的同步也可能会导致查询速度大幅度下降。

相关文章

C# this关键字的作用

关键字在C#中主要用于引用当前对象,区分字段与局部变量,调用其他构造函数以及传递当前对象给其他方法或构造函数。

C# 如何实现一个事件总线

EventBus(事件总线)是一种用于在应用程序内部或跨应用程序组件之间进行事件通信的机制。它允许不同的组件通过发布和订阅事件来进行解耦和通信。在给定的代码片段中,我们可以看到一个使用C#实现的Event Bus。它定义了一些接口和类来实现事件的发布和订阅。IEvent和。IEvent是一个空接口,用于约束事件的类型。是一个泛型接口,用于约束事件处理程序的类型。它定义了处理事件的异步方法HandleAsync和处理异常的方法HandleException。

如何在 Debian 12 上安装 Microsoft SQL Server?

在安装 Microsoft SQL Server 之前,我们需要确保系统是最新的,并安装一些必要的软件和依赖项。以下是详细的步骤:这将更新软件包列表并升级已安装的软件包。这将安装 curl 用于下载文件,gnupg 用于导入 GPG 密钥,以及 apt-transport-https 用于通过 HTTPS 访问软件包。

C# winfrom中excel文件导入导出

在C#交流群里,看到很多小伙伴在excel数据导入导出到C#界面上存在疑惑,所以今天专门做了这个主题,希望大家有所收获!环境:win10+vs2017界面:主要以演示为主,所以没有做优化,然后主界面上添加两个按钮,分别命名为ExportExcel和ImportExcel,添加两个dataGridView,分别是dataGridView1和dataGridView2然后在窗体加载程序中给dataGr...

C#中的浅度和深度复制(C#如何复制一个对象)

接着,我们修改了复制得到的对象及其引用类型字段的属性值,最后输出原始对象和复制对象的属性值。这意味着如果一个类包含引用类型成员,在执行深度复制时,不仅复制这些引用,还会递归地复制引用所指向的对象,直到所有的引用都指向全新的对象实例。当进行浅复制时,系统会创建一个新的对象实例,但这个新对象的字段将与原始对象中的值类型字段具有相同的值,而对于引用类型字段,则仅仅是复制了。也就是说,如果一个类中有引用类型的成员变量(比如数组、其他自定义类的对象等),那么浅复制后,新对象和原对象的这些引用类型成员仍然指向。

C# 实现微信自定义分享

在实际的应用中,我们可能不是简单的将该网页的链接直接分享出去,而是生成符合实际需要的URL,微信称其为自定义分享。

C# 常用排序算法(冒泡排序 插入排序 选择排序 快速排序 归并排序 堆排序)

建堆阶段将无序列表转换为堆,排序阶段将堆的根节点依次取出,并调整堆,完成排序。它使用分治法的思想,通过选择一个基准元素,将列表分成两个子列表,并对每个子列表递归地进行排序。它重复地遍历要排序的列表,比较相邻的两个元素,并交换它们的位置,直到列表排序完成为止。每次遍历都会将最大的元素移动到列表的末尾。每次选择未排序部分的最小元素,并将其放到已排序部分的末尾,逐步构建有序序列。它将列表分成较小的子列表,对每个子列表进行排序,然后再将子列表合并成较大的有序列表,直到整个列表排序完成。

.Net Core Policy 基于策略授权

在ASP.NET Core中,重新设计了一种更加灵活的授权方式:基于策略的授权, 它是授权的核心.在使用基于策略的授权时,首先要定义授权策略,而授权策略本质上就是对Claims的一系列断言。基于角色的授权和基于Scheme的授权,只是一种语法上的便捷,最终都会生成授权策略。除了OperationAuthorizationRequirement外,都有对应的快捷添加方法,比如RequireClaim,RequireRole,RequireUserName等。新建类 PermissionHandler。

FluentValidation在C# WPF中的应用

1. 引言在.NET开发领域,FluentValidation以其优雅、易扩展的特性成为开发者进行属性验证的首选工具。它不仅适用于Web开发,如MVC、Web API和ASP.NET CORE,同样也能完美集成在WPF应用程序中,提供强大的数据验证功能。本文将深入探讨如何在C# WPF项目中运用FluentValidation进行属性验证,并展示如何通过MVVM模式实现这一功能。2. 功能概览我们的目标是构建一个WPF应用程序,它能够通过FluentValidation实现以下验证功能:验证Vie

【.NET Core】Lazy<T> 实现延迟加载详解

延迟初始化是一种将对象的创建延迟到第一次需要用时的技术。简而言之,就是对象的初始化发生在第一次需要调用的时候执行。通常所说的延迟初始化和延迟实例化的意思是相同。通过使用延迟基础,可以避免应用程序不必要的计算和内存消耗。从.NET 4.0开始,可以使用Lazy来实现对象的延迟初始化,从而优化系统的性能。延迟初始化就是将对象的初始化延迟到第一次使用该对象时。延迟初始化是我们优化程序性能的一种方式。如创建一个对象时需要花费很大的开销,而这一对象在系统运行过程中不一定会用到。

各版本 操作系统 对 .NET Framework 与 .NET Core 支持

有两种类型的受支持版本:长期支持 (LTS) 版本和标准期限支持 (STS) 版本。所有版本的质量都是一样的。唯一的区别是支持的时间长短。LTS 版本可获得为期三年的免费支持和补丁。STS 版本可获得 18 个月的免费支持和修补程序。有关详细信息,请参阅。从上图中我们可以看出,.Net5及以下版本已经不再受到官方支持;而.Net7看起来也是过渡版本,支持时间较短,本文从 .Net Core 3.1 开始介绍支持的系统,可能不是很全面,仅供参考。

【C#】.net core 6.0 依赖注入生命周期

对于.net core而言,依赖注入生命周期有三种瞬态(Transient)、作用域(Scoped)和单例(Singleton),无论使用哪种生命周期,都需要确保对象的线程安全性,并正确地处理依赖关系。

C#动态生成带参数的小程序二维码

在微信小程序管理后台,我们可以生成下载标准的小程序二维码,提供主程序入口功能。在实际应用开发中,小程序二维码是可以携带参数的,可以动态进行生成
返回
顶部