惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
Forbes - Security
Forbes - Security
The Last Watchdog
The Last Watchdog
F
Full Disclosure
GbyAI
GbyAI
G
Google Developers Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
C
Check Point Blog
S
Security @ Cisco Blogs
H
Help Net Security
N
News | PayPal Newsroom
D
DataBreaches.Net
L
LINUX DO - 最新话题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
U
Unit 42
WordPress大学
WordPress大学
月光博客
月光博客
Security Latest
Security Latest
V
V2EX
Schneier on Security
Schneier on Security
美团技术团队
Y
Y Combinator Blog
G
GRAHAM CLULEY
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Securelist
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
P
Proofpoint News Feed
O
OpenAI News
Cisco Talos Blog
Cisco Talos Blog
Martin Fowler
Martin Fowler
N
News and Events Feed by Topic
小众软件
小众软件
S
Schneier on Security
Webroot Blog
Webroot Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog
T
Threatpost
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Scott Helme
Scott Helme
N
Netflix TechBlog - Medium
有赞技术团队
有赞技术团队
W
WeLiveSecurity
S
SegmentFault 最新的问题
Cloudbric
Cloudbric
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角
《C#多线程编程实战》读书笔记 - 元视角
飞鸿踏雪 · 2018-01-08 · via 元视角

本文是一篇读书笔记,由《C#多线程编程实战》一书中的内容整理而来,主要梳理了.NET 中多线程编程相关的知识脉络,从 Thread、ThreadPool、Task、async/await、并发集合、Parallel、PLINQ 到 Rx 及异步 I/O 等内容,均有所覆盖。为了帮助大家理解本文内容,首先给出博主在阅读该书过程中绘制的思维导图,大家可以根据个人需要针对性的查漏补缺。

《多线程编程实战》思维导图 《多线程编程实战》思维导图

线程基础

  • Tips1:暂停线程,即通过 Thread.Sleep()方法让线程等待一段时间而不用消耗操作系统资源。当线程处于休眠状态时,它会占用尽可能少的 CPU 时间。
  • Tips2:线程等待,即通过 Join()方法等待另一个线程结束,因为不知道执行所需要花费的时间,此时 Thread.Sleep()方法无效,并且第一个线程等待时是处于阻塞状态的。
  • Tips3:终止线程,调用 Abort()方法会给线程注入 ThreadAbortException 异常,该异常会导致程序崩溃,且该方法不一定总是能终止线程,目标线程可以通过处理该异常并调用 Thread.ResetAbort()方法来拒绝被终止,因此不推荐使用 Abort()方法来终止线程,理想的方式是通过 CancellationToken 来实现线程终止。
  • Tips4:线程优先级,线程优先级决定了该线程可占用多少 CPU 时间,通过设置 IsBackground 属性可以指定一个线程是否为后台线程,默认情况下,显式创建的线程都是前台线程。其主要区别是:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。需要注意的是,如果程序定义了一个不会赞成的前台线程,主程序并不会正常结束。
  • Tips5:向线程传递参数,可以通过 ThreadStart 或者 lambda 表达式来向一个线程传递参数,需要注意的是,由 lambda 表达式带来的闭包问题
  • Tips6:竞争条件是多线程环境中非常常见的导致错误的原因,通过 lock 关键字锁定一个静态对象(static&readonly)时,需要访问该对象的所有其它线程都会处于阻塞状态,并等待直到该对象解除锁定,这可能会导致严重的性能问题,
  • Tips7:发生死锁的原因是锁定的静态对象永远无法解除锁定,通常 Monitor 类用以解除死锁,而 lock 关键字用以创建死锁,Monitor 类的 TryEnter()方法可以用以检测静态对象是否可以解锁,lock 关键字本质上是 Monitor 类的语法糖。
bool acquiredLock = false;
try
{
  Monitor.Enter(lockObject, ref acquiredLock)
}
finally
{
  if(acquiredLock)
  {
    Monitor.Exit(lockObject)
  }
}
  • Tips8:不要在线程中抛出异常,而是在线程代码中使用 try……catch 代码块。

线程同步

  • Tips9:无须共享对象,则无须进行线程同步,通过重新设计程序来移除共享状态,从而避免复杂的同步构造;使用原子操作,这意味着一个操作只占用一个量子的时间,一次就可以完成,并且只有当前操作完成后,其它线程方可执行其它操作,因此,无须实现其它线程等待当前操作完成,进而避免了使用锁,排除了死锁。
  • Tips10:为了实现线程同步,我们不得不使用不同的方式来协调线程,方式之一是将等待的线程设为阻塞,当线程处于阻塞状态时,会占用尽可能少的 CPU 时间,然而这意味着会引入至少一次的上下文切换。上下文切换,是指操作系统的线程调度器,该调度器会保存等待的线程状态,并切换到另一个线程,依次恢复等待的线程状态,而这需要消耗更多的资源。
  • Tips11:线程调度模式,当线程挂起很长时间时,需要操作系统内核来阻止线程使用 CPU 时间,这种模式被称为内核模式;当线程只需要等待一小段时间,而不需要将线程切换到阻塞状态,这种模式被称为用户模式;先尝试按照用户模式进行等待,如线程等待足够长时间,则切换到阻塞状态以节省 CPU 资源,这种模式被称为混合模式。
  • Tips12:Mutex 是一种原始的同步方法,其只对一个线程授予对共享资源的独占访问,Mutex 可以在不同的程序中同步线程。
  • Tips13:SemaphoreSlim 是 Semaphore 的轻量级版本,用以限制同时访问同一个资源的线程数量,超过该数量的线程需要等待,直到之前的线程中某一个完成工作,并调用 Release()方法发出信号,其使用了混合模式,而 Semaphore 则使用内核模式,可以在跨程序同步的场景下使用。
  • Tips14:AutoResetEvent 类用以从一个线程向另一个线程发送通知,该类可以通知等待的线程有某个事件发生,其实例在默认情况下初始状态为 unsignaled,调用 WaitOne()方法时将会被阻塞,直到我们调用了 Set 方法;相反地,如果初始状态为 signaled,调用 WaitOne()方法时将会被立即处理,需要我们再调用一次 Set 方法,以便向其它线程发出信号。
  • Tips15:ManualResetEventSlim 类是使用混合模式的线程信号量,相比使用内核模式的 AutoResetEvent 类更好(因为等待时间不能太长),AutoResetEvent 像一个旋转门,一次仅允许一个人通过,而 ManualResetEventSlim 是 ManualResetEvent 的混合版本,一直保持大门开启直到手动屌用 Reset 方法。
  • Tips16:EventWaitHandle 类是 AutoResetEvent 和 ManualResetEvent 的基类,可以通过调用其 WaitOne()方法来阻塞线程,直到 Set()方法被调用,它有两种状态,即终止态和非终止态,这两种状态可以相互转换,调用 Set()方法可将其实例设为终止态,调用 Reset()方法可以将其实例设为非终止态。
  • Tips17:CountdownEvent 类可以用以等到直到一定数量的操作完成,需要注意的是,如果其实例方法 Signal()没有达到指定的次数,则其实例方法 Wait()将一直等待。所以,请确保使用 CountdownEvent 时,所有线程完成后都要调用 Signal()方法。
  • Tips18:ReaderWriterLockSlim 用以创建一个线程安全的机制,在多线程中对一个集合进行读写操作,ReaderWriterLockSlim 代表了一个管理资源访问的锁,允许多个线程同时读取,以及独占写。其中,读锁允许多线程读取数据,写锁在被释放前会阻塞其它线程的所有操作。
  • Tips19:SpinWait 类是一个混合同步构造,被设计为使用用户模式等待一段时间,然后切换到内核模式以节省 CPU 时间。

使用线程池

  • Tips20:volatile 关键字指出一个字段可能会被同时执行的多个线程修改,声明为 volatile 的字段不会被编译器和处理器优化为只能被单线程访问。
  • Tips21:创建线程是昂贵的操作,所以为每个短暂的异步操作创建线程会产生显著的开销。线程池的用途是执行运行时间短的操作,使用线程池可以减少并行度耗费及节省操作系统资源。在 ASP.NET 应用程序中使用线程池时要相当小心,ASP.NET 基础切实使用自己的线程池,如果在线程池中浪费所有的工作者线程,Web 服务器将不能够服务新的请求,在 ASP.NET 中只推荐使用 I/O 密集型的异步操作,因为其使用了一个不同的方式,叫做 I/O 线程。
  • Tips22:APM,即异步编程模型,是指使用 BeginXXX/EndXXX 和 IAsyncResult 对象等方式,其通过调用 BeginInvoke 方法返回 IAsyncResult 对象,然后通过调用 EndInvoke 方法返回结果,我们可通过轮询 IAsyncResult 对象的 IsCompleted 或者调用 IAsyncResult 对象的 AsyncWaitHandle 属性的 WaitOne()方法来等待直到操作完成。
  • Tips23:ThreadPool.RegisterWaitForSingleObject()方法允许我们将回调函数放入线程池中的队列中,当提供的等待事件处理器收到信号或发生超时时,该回调函数将被调用,这做鱼我们为线程池中的操作实现超时功能。具体思路是:ManualResetEvent + CancellationToken,当接收到 ManualResetEvent 对象的信号时处理超时,或者是使用 CancellationToken 来处理超时。
  • Tips24:CancellationToken 是.NET4.0 中被引入的实现异步操作的取消操作的事实标准,我们可以使用三种方式来实现取消过程,即轮询 IsCancellationRequested 属性、抛出 OperationCanceledException 异常、为 CancellationToken 注册一个回调函数。
  • Tips25:Timer 对象用以在线程池中创建周期性调用的异步操作。
  • Tips26:BackgroundWorker 组件,是典型的基于事件的异步模式,即 EAP,当通过 RunWorkerAsync 启动一个异步操作时,DoWork 事件所订阅的事件处理器,将会运行在线程池中,如果需要需要取消异步操作,则可以调用 CancelAsync()方法。

使用任务并行库

  • Tips27:TPL 即任务并行库,在.NET 4.0 中被引入,目的是解决 APM 和 EAP 中获取结果和传播异常的问题,TPL 在.NET4.5 中进行了调整,使其在使用上更简单,它可以理解为线程池之上的又一个抽象层,对程序员隐藏了与线程池交互的底层代码,并提供了更方便的细粒度的 API。TPL 的核心概念是任务,一个任务代表了一个异步操作,该操作可以通过多种方式运行,可以使用或者不使用独立线程运行。TPL 相比之前的模式,一个关键优势是其具有用于组合任务的便利的 API。
  • Tips28:Task.Run 是 Task.Factory.StartNew 的一个快捷方式,后者有附加的选项,在无特殊需求的情况下,可以直接使用 Task.Run,通过 TaskScheduler,我们可以控制任务的运行方式。
  • Tips29:使用 Task 实例的 Start 方法启动任务并等待结果,该任务会被放置在线程池中并且主线程会等待,直到任务返回前一直处于阻塞状态;使用 Task 实例的 RunSynchronously 方法启动任务,该任务是运行在主线程中,这是一个非常好的优化,可以避免使用线程池来执行非常短暂的操作;我们可以通过轮询 Task 实例的状态信息来判断一个任务是否执行结束。
  • Tips30:通过 Task 实例的 ContinueWith 方法可以为任务设置一个后续操作,通过 TaskContinuationOptions 选项来指定后续任务以什么样的方式执行。
  • Tips31:通过 Task 实例的 FromAsync 可以实现 APM 到 Task 的转换
  • Tips32:通过 TaskCompletionSource 可以实现 EAP 到 Task 的转换
  • Tips33:TaskScheduler 是一个非常重要的抽象,该组件实际上负责如何执行任务,默认的任务调度程序将任务放置在线程池的工作线程中。为了避免死锁,绝对不要通过任务调度程序在 UI 线程中使用同步操作,请使用 ContinueWith 或 async/await 方法。

使用 C# 6.0

  • Tips34:异步函数是 C# 5.0 引入的语言特性,它是基于 TPL 之上的更高级别抽象,真正简化了异步编程。要创建一个异步函数,首先需要使用 async 关键字标注一个方法,其次异步函数必须返回 Task 或 Task 类型,可以使用 async void 的方法,但是更推荐 async Task 的方法,使用 async void 的方法的唯一合理的地方就是在程序中使用顶层 UI 控制器事件处理器的时候,在使用 async 关键字标注的方法内部,可以使用 await 操作符,该操作符可与 TPL 任务一起工作,并获取该任务中异步操作的结果,在 async 方法外部不能使用 await 关键字,否则会有编译错误,异步函数代码中至少要拥有一个 await 关键字。
  • Tips35:在 Windows GUI 或 ASP.NET 等环境中不推荐使用 Task.Wait 和 Task.Result,因为非常有可能会造成死锁。 async 可以和 lambda 表达式联用,在表达式体中应该至少含有一个 await 关键字标示,因为 lambda 表达式的类型无法通过自身推断,所以必须显式地向 C#编译器指定类型。
  • Tips36:异步并不总是意味着并行执行
  • Tips37:单个异步操作可以使用 try……catch 来捕获异常,而对于一个以上的异步操作,使用 try…catch 仅仅可以从底层的 AggregateException 对象中获得第一个异常,为了获得所有的异常,可以使用 AggregateException 的 Flatten()方法将层级异常放入一个列表,并从中提取出所有的底层异常。
  • Tips38:通过 Task 实例的 ConfigureAwait()方法,可以设置使用 await 时同步上下文的行为,默认情况下,await 操作符会尝试捕捉同步上下文,并在其中执行代码,即调度器会向 UI 线程投入成千上百个后续操作任务,这会使用它的消息循环来异步地执行这些任务,当我们不需要在 UI 线程中运行这些代码时,向 ConfigureAwait 方法传入 false 将会是一个更高效的方案。
  • Tips39:async void 方法会导致异常处理方法,会放置到当前的同步上下文中,因此线程池中未被处理的异常会终结整个进程,使用 AppDomain.UnhandledException 事件可以拦截未处理的异常,但不能从拦截的地方恢复进程,async void 的 lambda 表达式,同 Action 类型是兼容的,强烈建议仅仅在 UI 事件处理器中使用 async void 方法,在其他情况下,请使用返回 Task 或者 Task 的方法。

使用并行集合

  • Tips40:ConcurrentQueue 使用了原子的比较和交换(CAS),以及 SpinWait 来保证线程安全,它实现了一个先进先出(FIFO)的集合,这意味着元素出队列的顺序与加速队列的顺序是一致的,可以调用 Enqueue 方法向对接中加入元素,调用 TryDequeue 方法试图取出队列中第一个元素,调用 TryPeek 方法试图得到第一个元素但并不从队列中删除该元素。
  • Tips41:ConcurrentStack 的实现同样没有使用锁,仅采用了 CAS 操作,它是一个后进先出(LIFO)的集合,这意味着最后添加的元素会先返回,可以调用 Push 和 PushRange 方法添加元素,使用 TryPop 和 TryPopRange 方法获取元素,使用 TryPeek 方法检查元素。
  • Tips42:ConcurrentBag 是一个支持重复元素的无序集合,它针对以下情况进行了优化,即多个线程以这样的方式工作:每个线程产生和消费其自身的任务,极少发生线程间的交互(因为要交互就要使用锁)。可以调用 Add 方法添加元素,调用 TryPeek 方法检查元素,调用 TryTake 方法获取元素。
  • Tips43:ConcurrentDictionary 是一个线程安全的字典集合的实现,对于读操作无需使用锁,对于写操作则需要使用锁,该并发字典使用多个锁,在字典桶之上实现了一个细粒度的锁模型(使用锁的常规字典称为粗粒度锁),参数 concurrentLevel 可以在构造函数中定义锁的数量。这意味着预估的线程数量将并发地更新该字典。由于并发字典使用锁,如无必要请避免使用以下操作:Count、IsEmpty、Keys、Values、CopyTo 及 ToArray,因为需要获取该字典中的所有锁。
  • Tips44:BlockingCollection 是一个针对 IProducerConsumerCollection 泛型接口实现的高级封装,它有很多先进的功能来实现管道场景,即当你有一些步骤需要使用之前步骤运行的结果时。BlockingCollection 类支持分块、调整内部集合容量、取消集合操作、从多个块集合中获取元素等。
  • Tips45:对 BlockingCollection 进行迭代时,需要注意的是,使用 GetConsumingEnumerable()进行迭代,因为虽然 BlockingCollection 实现了 IEnumerable 接口,但是它默认的行为是表示集合的“快照”,这不是我们期望的行为。

使用 PLINQ

  • Tips46:将程序分割成一组任务并使用不同的线程来运行不同的任务,这种方式被称为任务并行 将数据分割成较小的数据块,对这些数据进行并行计算,然后聚合这些计算结果,这种编程模型称为数据并行
  • Tips47:结构并行确实更易维护,应该尽可能地使用,但它并不是万能的。通常有很多情况我们是不能简单地使用结构并行,那么以非结构化的方式使用 TPL 任务并行也是完全可以的。 Parallel 类中的 Invoke 方法是最简单的实现多任务并行的方法,Invoke 方法会阻塞其它线程直到所有线程都完成。
  • Tips48:Parallel 类中的 For 和 ForEach 方法可以定义并行循环,通过传入一个委托来定义每个循环项的行为,并得到一个结果来说明循环是否成功完成,ParallelOptions 类可以为并行循环定义最大并行数,使用 CollectionToken 取消任务,使用 TaskScheduler 类调度任务。
  • Tips49:ParallelLoopState 可以用于从循环中跳出或者检查循环状态,它有两种方式:Break 和 Stop,Stop 是指循环停止处理任何工作,而 Break 是指停止其之后的迭代,继续保持其之前的迭代工作。
  • Tips50:同 Task 类似,当使用 AsParallel()方法并行查询时,我们将得到 AggregateException,它将包含运行 PLINQ 期间发生的所有异常,我们可以使用 Flatten()方法和 Handle()方法来处理这些异常。
  • Tips51:ParallelEnumerable 类含有 PLINQ 的全部逻辑,并且作为 IEnumerable 集合功能的一组扩展方法,默认情况下结果会被合并单个线程中,我们可以通过 ForAll 方法来指定处理逻辑,此时它们使用的是同一个线程,将跳过合并结果的过程,除了 AsParallel()方法,我们同样可以使用 AsSequential()方法,来使得 PLINQ 查询以顺序方式执行(相对于并行)
  • Tips52:PLINQ 中提供了丰富用以 PLINQ 查询的选项,例如 WithCancellation()方法用以取消查询,这将导致引发 OperationCanceledException 异常,并取消剩余的工作;例如 WithDegreeOfParallelism()方法用以指定执行查询时实际并行分割数,可以决定并行执行会占用多少资源及其性能如何;例如 WithExecutionMode()可以重载查询执行的模式,即我们可以决定选择以顺序执行还是并行执行的方式去执行查询;例如 WithMergeOptions()方法可以用以调整对查询结果的处理,默认 PLINQ 会将结果合并到单个线程中,因此在查询结果返回前,会缓存一定数量的结果,当发现查询花费大量时间时,更合理的方式是关闭结果缓存从而尽可能快地得到结果;例如 AsOrdered()方法,用以告诉 PLINQ 我们希望按照集合中的顺序进行处理(并行条件下,集合中的项有可能不是按顺序被处理的)

使用异步 I/O

  • Tips53:异步 I/O,对服务器而言,可伸缩性是最高优先级,这意味着单个用户消耗的资源越少越好,如果为每个用户创建多个线程,则可伸缩性并不好,在 I/O 密集型的场景中需要使用异步,因为不需要 CPU 工作,其瓶颈在磁盘上,这种执行 I/O 任务的方式成为 I/O 线程。 在异步文件读写中,FileOptions.Asynchronous 是一个非常重要的选项,无论有无此参数都可以,以异步的方式使用该文件,区别是前者仅仅是在线程池中异步委托调用,而后者可以对 FileStream 垒使用异步 I/O。
  • Tips54:对 HttpListener 类,我们可以通过 GetContextasync()方法来异步地获取上下文。
  • Tips55:对数据库而言,我们可以通过 OpenAsync()、ExecuteNonQueryAsync()等方法异步地执行 SQL 语句。

好了,以上就是这篇读书笔记的主要内容啦,听说掌握了这 55 条 Tips 的人,都敢在简历上写”精通多线程编程“,哈哈,晚安啦,各位!