C#多线程编程系列(四)- 使用线程池

目录

  • 1.1 简介
  • 1.2 在线程池中调用委托
  • 1.3 向线程池中放入异步操作
  • 1.4 线程池与并行度
  • 1.5 实现一个取消选项
  • 1.6 在线程池中使用等待事件处理器及超时
  • 1.7 使用计时器
  • 1.8 使用BackgroundWorker组件
  • 参考书籍
  • 笔者水平有限,如果错误欢迎各位批评指正!


1.1 简介#

在本章中,主要介绍线程池(ThreadPool)的使用;在C#中它叫System.Threading.ThreadPool,在使用线程池之前首先我们得明白一个问题,那就是为什么要使用线程池。其主要原因是创建一个线程的代价是昂贵的,创建一个线程会消耗很多的系统资源。

那么线程池是如何解决这个问题的呢?线程池在初始时会自动创建一定量的线程供程序调用,使用时,开发人员并不直接分配线程,而是将需要做的工作放入线程池工作队列中,由线程池分配已有的线程进行处理,等处理完毕后线程不是被销毁,而是重新回到线程池中,这样节省了创建线程的开销。

但是在使用线程池时,需要注意以下几点,这将非常重要。

  • 线程池不适合处理长时间运行的作业,或者处理需要与其它线程同步的作业。
  • 避免将线程池中的工作线程分配给I/O首先的任务,这种任务应该使用TPL模型。
  • 如非必须,不要手动设置线程池的最小线程数和最大线程数,CLR会自动的进行线程池的扩张和收缩,手动干预往往让性能更差。

1.2 在线程池中调用委托#

本节展示的是如何在线程池中如何异步的执行委托,然后将介绍一个叫异步编程模型(Asynchronous Programming Model,简称APM)的异步编程方式。

在本节及以后,为了降低代码量,在引用程序集声明位置默认添加了using static System.Consoleusing static System.Threading.Thead声明,这样声明可以让我们在程序中少些一些意义不大的调用语句。

演示代码如下所示,使用了普通创建线程和APM方式来执行同一个任务。

 

Copy

static void Main(string[] args) { int threadId = 0; RunOnThreadPool poolDelegate = Test; var t = new Thread(() => Test(out threadId)); t.Start(); t.Join(); WriteLine($"手动创建线程 Id: {threadId}"); // 使用APM方式 进行异步调用 异步调用会使用线程池中的线程 IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "委托异步调用"); r.AsyncWaitHandle.WaitOne(); // 获取异步调用结果 string result = poolDelegate.EndInvoke(out threadId, r); WriteLine($"Thread - 线程池工作线程Id: {threadId}"); WriteLine(result); Console.ReadLine(); } // 创建带一个参数的委托类型 private delegate string RunOnThreadPool(out int threadId); private static void Callback(IAsyncResult ar) { WriteLine("Callback - 开始运行Callback..."); WriteLine($"Callback - 回调传递状态: {ar.AsyncState}"); WriteLine($"Callback - 是否为线程池线程: {CurrentThread.IsThreadPoolThread}"); WriteLine($"Callback - 线程池工作线程Id: {CurrentThread.ManagedThreadId}"); } private static string Test(out int threadId) { string isThreadPoolThread = CurrentThread.IsThreadPoolThread ? "ThreadPool - ": "Thread - "; WriteLine($"{isThreadPoolThread}开始运行..."); WriteLine($"{isThreadPoolThread}是否为线程池线程: {CurrentThread.IsThreadPoolThread}"); Sleep(TimeSpan.FromSeconds(2)); threadId = CurrentThread.ManagedThreadId; return $"{isThreadPoolThread}线程池工作线程Id: {threadId}"; }

运行结果如下图所示,其中以Thread开头的为手动创建的线程输出的信息,而TheadPool为开始线程池任务输出的信息,Callback为APM模式运行任务结束后,执行的回调方法,可以清晰的看到,Callback的线程也是线程池的工作线程。

1533523675523

在上文中,使用BeginOperationName/EndOperationName方法和.Net中的IAsyncResult对象的方式被称为异步编程模型(或APM模式),这样的方法被称为异步方法。使用委托的BeginInvoke方法来运行该委托,BeginInvoke接收一个回调函数,该回调函数会在任务处理完成后背调用,并且可以传递一个用户自定义的状态给回调函数。

现在这种APM编程方式用的越来越少了,更推荐使用任务并行库(Task Parallel Library,简称TPL)来组织异步API。

1.3 向线程池中放入异步操作#

本节将介绍如何将异步操作放入线程池中执行,并且如何传递参数给线程池中的线程。本节中主要用到的是ThreadPool.QueueUserWorkItem()方法,该方法可将需要运行的任务通过委托的形式传递给线程池中的线程,并且允许传递参数。

使用比较简单,演示代码如下所示。演示了线程池使用中如何传递方法和参数,最后需要注意的是使用了Lambda表达式和它的闭包机制。

 

Copy

static void Main(string[] args) { const int x = 1; const int y = 2; const string lambdaState = "lambda state 2"; // 直接将方法传递给线程池 ThreadPool.QueueUserWorkItem(AsyncOperation); Sleep(TimeSpan.FromSeconds(1)); // 直接将方法传递给线程池 并且 通过state传递参数 ThreadPool.QueueUserWorkItem(AsyncOperation, "async state"); Sleep(TimeSpan.FromSeconds(1)); // 使用Lambda表达式将任务传递给线程池 并且通过 state传递参数 ThreadPool.QueueUserWorkItem(state => { WriteLine($"Operation state: {state}"); WriteLine($"工作线程 id: {CurrentThread.ManagedThreadId}"); Sleep(TimeSpan.FromSeconds(2)); }, "lambda state"); // 使用Lambda表达式将任务传递给线程池 通过 **闭包** 机制传递参数 ThreadPool.QueueUserWorkItem(_ => { WriteLine($"Operation state: {x + y}, {lambdaState}"); WriteLine($"工作线程 id: {CurrentThread.ManagedThreadId}"); Sleep(TimeSpan.FromSeconds(2)); }, "lambda state"); ReadLine(); } private static void AsyncOperation(object state) { WriteLine($"Operation state: {state ?? "(null)"}"); WriteLine($"工作线程 id: {CurrentThread.ManagedThreadId}"); Sleep(TimeSpan.FromSeconds(2)); }

运行结果如下图所示。

1533537253353

1.4 线程池与并行度#

在本节中,主要是使用普通创建线程和使用线程池内的线程在任务量比较大的情况下有什么区别,我们模拟了一个场景,创建了很多不同的线程,然后分别使用普通创建线程方式和线程池方式看看有什么不同。

 

Copy

static void Main(string[] args) { const int numberOfOperations = 500; var sw = new Stopwatch(); sw.Start(); UseThreads(numberOfOperations); sw.Stop(); WriteLine($"使用线程执行总用时: {sw.ElapsedMilliseconds}"); sw.Reset(); sw.Start(); UseThreadPool(numberOfOperations); sw.Stop(); WriteLine($"使用线程池执行总用时: {sw.ElapsedMilliseconds}"); Console.ReadLine(); } static void UseThreads(int numberOfOperations) { using (var countdown = new CountdownEvent(numberOfOperations)) { WriteLine("通过创建线程调度工作"); for (int i = 0; i < numberOfOperations; i++) { var thread = new Thread(() => { Write($"{CurrentThread.ManagedThreadId},"); Sleep(TimeSpan.FromSeconds(0.1)); countdown.Signal(); }); thread.Start(); } countdown.Wait(); WriteLine(); } } static void UseThreadPool(int numberOfOperations) { using (var countdown = new CountdownEvent(numberOfOperations)) { WriteLine("使用线程池开始工作"); for (int i = 0; i < numberOfOperations; i++) { ThreadPool.QueueUserWorkItem(_ => { Write($"{CurrentThread.ManagedThreadId},"); Sleep(TimeSpan.FromSeconds(0.1)); countdown.Signal(); }); } countdown.Wait(); WriteLine(); } }

执行结果如下,可见使用原始的创建线程执行,速度非常快。只花了2秒钟,但是创建了500多个线程,而使用线程池相对来说比较慢,花了9秒钟,但是只创建了很少的线程,为操作系统节省了线程和内存空间,但花了更多的时间。

1533540572298

1.5 实现一个取消选项#

在之前的文章中有提到,如果需要终止一个线程的执行,那么可以使用Abort()方法,但是有诸多的原因并不推荐使用Abort()方法。

这里推荐的方式是使用协作式取消(cooperative cancellation),这是一种可靠的技术来安全取消不再需要的任务。其主要用到CancellationTokenSourceCancellationToken两个类,具体用法见下面演示代码。

以下延时代码主要是实现了使用CancellationTokenCancellationTokenSource来实现任务的取消。但是任务取消后可以进行三种操作,分别是:直接返回、抛出ThrowIfCancellationRequesed异常和执行回调。详细请看代码。

 

Copy

static void Main(string[] args) { // 使用CancellationToken来取消任务 取消任务直接返回 using (var cts = new CancellationTokenSource()) { CancellationToken token = cts.Token; ThreadPool.QueueUserWorkItem(_ => AsyncOperation1(token)); Sleep(TimeSpan.FromSeconds(2)); cts.Cancel(); } // 取消任务 抛出 ThrowIfCancellationRequesed 异常 using (var cts = new CancellationTokenSource()) { CancellationToken token = cts.Token; ThreadPool.QueueUserWorkItem(_ => AsyncOperation2(token)); Sleep(TimeSpan.FromSeconds(2)); cts.Cancel(); } // 取消任务 并 执行取消后的回调函数 using (var cts = new CancellationTokenSource()) { CancellationToken token = cts.Token; token.Register(() => { WriteLine("第三个任务被取消,执行回调函数。"); }); ThreadPool.QueueUserWorkItem(_ => AsyncOperation3(token)); Sleep(TimeSpan.FromSeconds(2)); cts.Cancel(); } ReadLine(); } static void AsyncOperation1(CancellationToken token) { WriteLine("启动第一个任务."); for (int i = 0; i < 5; i++) { if (token.IsCancellationRequested) { WriteLine("第一个任务被取消."); return; } Sleep(TimeSpan.FromSeconds(1)); } WriteLine("第一个任务运行完成."); } static void AsyncOperation2(CancellationToken token) { try { WriteLine("启动第二个任务."); for (int i = 0; i < 5; i++) { token.ThrowIfCancellationRequested(); Sleep(TimeSpan.FromSeconds(1)); } WriteLine("第二个任务运行完成."); } catch (OperationCanceledException) { WriteLine("第二个任务被取消."); } } static void AsyncOperation3(CancellationToken token) { WriteLine("启动第三个任务."); for (int i = 0; i < 5; i++) { if (token.IsCancellationRequested) { WriteLine("第三个任务被取消."); return; } Sleep(TimeSpan.FromSeconds(1)); } WriteLine("第三个任务运行完成."); }

运行结果如下所示,符合预期结果。

1533547890589

1.6 在线程池中使用等待事件处理器及超时#

本节将介绍如何在线程池中使用等待任务和如何进行超时处理,其中主要用到ThreadPool.RegisterWaitForSingleObject()方法,该方法允许传入一个WaitHandle对象,和需要执行的任务、超时时间等。通过使用这个方法,可完成线程池情况下对超时任务的处理。

演示代码如下所示,运行了两次使用ThreadPool.RegisterWaitForSingleObject()编写超时代码的RunOperations()方法,但是所传入的超时时间不同,所以造成一个必然超时和一个不会超时的结果。

 

Copy

static void Main(string[] args) { // 设置超时时间为 5s WorkerOperation会延时 6s 肯定会超时 RunOperations(TimeSpan.FromSeconds(5)); // 设置超时时间为 7s 不会超时 RunOperations(TimeSpan.FromSeconds(7)); } static void RunOperations(TimeSpan workerOperationTimeout) { using (var evt = new ManualResetEvent(false)) using (var cts = new CancellationTokenSource()) { WriteLine("注册超时操作..."); // 传入同步事件 超时处理函数 和 超时时间 var worker = ThreadPool.RegisterWaitForSingleObject(evt , (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut) , null , workerOperationTimeout , true); WriteLine("启动长时间运行操作..."); ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt)); Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2))); // 取消注册等待的操作 worker.Unregister(evt); ReadLine(); } } static void WorkerOperation(CancellationToken token, ManualResetEvent evt) { for (int i = 0; i < 6; i++) { if (token.IsCancellationRequested) { return; } Sleep(TimeSpan.FromSeconds(1)); } evt.Set(); } static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut) { if (isTimedOut) { cts.Cancel(); WriteLine("工作操作超时并被取消."); } else { WriteLine("工作操作成功."); } }

运行结果如下图所示,与预期结果相符。

1533556752088

1.7 使用计时器#

计时器是FCL提供的一个类,叫System.Threading.Timer,可要结果与创建周期性的异步操作。该类使用比较简单。

以下的演示代码使用了定时器,并设置了定时器延时启动时间和周期时间。

 

Copy

static void Main(string[] args) { WriteLine("按下回车键,结束定时器..."); DateTime start = DateTime.Now; // 创建定时器 _timer = new Timer(_ => TimerOperation(start), null , TimeSpan.FromSeconds(1) , TimeSpan.FromSeconds(2)); try { Sleep(TimeSpan.FromSeconds(6)); _timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4)); ReadLine(); } finally { //实现了IDispose接口 要及时释放 _timer.Dispose(); } } static Timer _timer; static void TimerOperation(DateTime start) { TimeSpan elapsed = DateTime.Now - start; WriteLine($"离 {start} 过去了 {elapsed.Seconds} 秒. " + $"定时器线程池 线程 id: {CurrentThread.ManagedThreadId}"); }

运行结果如下所示,可见定时器根据所设置的周期时间循环的调用TimerOperation()方法。

1533557619004

1.8 使用BackgroundWorker组件#

本节主要介绍BackgroundWorker组件的使用,该组件实际上被用于Windows窗体应用程序(Windows Forms Application,简称 WPF)中,通过它实现的代码可以直接与UI控制器交互,更加自认和好用。

演示代码如下所示,使用BackgroundWorker来实现对数据进行计算,并且让其支持报告工作进度,支持取消任务。

 

Copy

static void Main(string[] args) { var bw = new BackgroundWorker(); // 设置可报告进度更新 bw.WorkerReportsProgress = true; // 设置支持取消操作 bw.WorkerSupportsCancellation = true; // 需要做的工作 bw.DoWork += Worker_DoWork; // 工作处理进度 bw.ProgressChanged += Worker_ProgressChanged; // 工作完成后处理函数 bw.RunWorkerCompleted += Worker_Completed; bw.RunWorkerAsync(); WriteLine("按下 `C` 键 取消工作"); do { if (ReadKey(true).KeyChar == 'C') { bw.CancelAsync(); } } while (bw.IsBusy); } static void Worker_DoWork(object sender, DoWorkEventArgs e) { WriteLine($"DoWork 线程池 线程 id: {CurrentThread.ManagedThreadId}"); var bw = (BackgroundWorker)sender; for (int i = 1; i <= 100; i++) { if (bw.CancellationPending) { e.Cancel = true; return; } if (i % 10 == 0) { bw.ReportProgress(i); } Sleep(TimeSpan.FromSeconds(0.1)); } e.Result = 42; } static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { WriteLine($"已完成{e.ProgressPercentage}%. " + $"处理线程 id: {CurrentThread.ManagedThreadId}"); } static void Worker_Completed(object sender, RunWorkerCompletedEventArgs e) { WriteLine($"完成线程池线程 id: {CurrentThread.ManagedThreadId}"); if (e.Error != null) { WriteLine($"异常 {e.Error.Message} 发生."); } else if (e.Cancelled) { WriteLine($"操作已被取消."); } else { WriteLine($"答案是 : {e.Result}"); } }

运行结果如下所示。

1533558846212

在本节中,使用了C#中的另外一个语法,叫事件(event)。当然这里的事件不同于之前在线程同步章节中提到的事件,这里是观察者设计模式的体现,包括事件源、订阅者和事件处理程序。因此,除了异步APM模式意外,还有基于事件的异步模式(Event-based Asynchronous Pattern,简称 EAP)

参考书籍

本文主要参考了以下几本书,在此对这些作者表示由衷的感谢你们提供了这么好的资料。

  1. 《CLR via C#》
  2. 《C# in Depth Third Edition》
  3. 《Essential C# 6.0》
  4. 《Multithreading with C# Cookbook Second Edition》
  5. 《C#多线程编程实战》

源码下载点击链接 示例源码下载

笔者水平有限,如果错误欢迎各位批评指正!

作者:InCerry

出处:https://www.cnblogs.com/InCerry/p/9432804.html

版权:本文采用「署名 4.0 国际」知识共享许可协议进行许可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438436.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#线程模型脉络

今天在看同事新买到的《C#本质论 Edition 4》的时候&#xff0c;对比下以前Edtion3的新特性时&#xff0c;针对Async/Await关键字时发现对一些线程方面的定义还理解的不是很透彻&#xff0c;脉络还不是很清晰&#xff0c;这样有了本文&#xff0c;希望对有同样困惑的朋友有些帮…

“菜”鸟理解.NET Framework(CLI,CLS,CTS,CLR,FCL,BCL)

既然要学.NET&#xff0c;就要先认识认识她&#xff0c;我不喜欢大段大段文字的东西&#xff0c;自己通过理解&#xff0c;画个图&#xff0c;来看看.NET的沉鱼落雁&#xff0c;闭月羞花之容。 最下层蓝色部分是.NET Framework的基础&#xff0c;也是所有应用软件的基础。.NET …

QQ炫舞手游显示进入服务器失败6,qq炫舞手游进不去怎么办 游戏进不去方法详解[多图]...

qq炫舞手游是新出的游戏&#xff0c;在近期非常的火爆&#xff0c;不过有不少的玩家都有进不了游戏的情况&#xff0c;下面安族小编给大家介绍一下游戏进不去方法详解。qq炫舞手游玩不了解决方法1.第一种方式就是内测还没有开启咯&#xff0c;所以玩家一般都是下载不到包的&…

那些年我们一起追逐的多线程(Thread、ThreadPool、委托异步调用、Task/TaskFactory、Parallerl、async和await)

一. 背景 在刚接触开发的头几年里&#xff0c;说实话&#xff0c;根本不考虑多线程的这个问题&#xff0c;貌似那时候脑子里也有没有多线程的这个概念&#xff0c;所有的业务都是一个线程来处理&#xff0c;不考虑性能问题&#xff0c;当然也没有考虑多线程操作一条记录存在的并…

asp.net mvc webform和razor的page基类区别

接触过asp.net mvc的都知道&#xff0c;在传统的webform的模式下&#xff0c;page页面的基类是这样声明的&#xff1a; <% Page Language"C#" MasterPageFile"~/Views/Shared/Site.Master" Inherits"ViewPage" %> 如果是partial view的话…

frameset ajax,js控制frameSet示例

js控制frameSet示例1&#xff1a;js修改frameset的cols属性来达到修改各个页面所占的宽高&#xff0c;例如隐藏当前frame页。复制代码 代码如下:window.parent.document.getElementsByTagName("frameset")[0].cols"0,*";2&#xff1a;js调用其他frame页面的…

人生的抉择—aspx、ashx、asmx文件处理请求效率比较

总结&#xff1a; ashx&#xff1a;只是实现IHttpHandler接口 aspx&#xff1a;public class Page : TemplateControl, IHttpHandler 而TemplateControl是&#xff1a; abstract class TemplateControl : Control, INamingContainer, IFilterResolutionService 所以aspx是重型…

ajax jsp模糊查询源码,Ajax动态执行模糊查询功能

Ajax动态执行模糊查询功能 内容精选换一换Profiling采集的数据较多&#xff0c;同时解析后展示的性能指标项也比较多&#xff0c;为方便用户快捷查找到具体性能指标的含义&#xff0c;提供命令行查询功能&#xff1a;不包含metric_name参数时&#xff0c;打印hiprof.sh脚本帮助…

一般处理程序(ashx)和页面处理程序(aspx)的区别

客官请看图 图中的Httphandler就是处理程序。 两者的共同点 如果把aspx处理程序和ashx处理程序放到上图中&#xff0c;他们是处在相同的位置的&#xff0c; 他们都实现了IHttphandler接口。实现了IHttphandler才具备处理请求的能力 两者的不同点 微软对aspx下足了功夫&#…

怎么在微云服务器找一个文件,微云文件在哪里打开_怎么快速找到微云文件

微云文件在哪里打开&#xff0c;这个问题困扰了许多的朋友&#xff0c;今天小编带着教程走向大家&#xff0c;我们可以使用微云可以快速连接和预览在线文档&#xff1b;同时加载和卸载多终端、手机、计算机、同步PAD数据传输&#xff0c;无需数据线。对于微云客户端&#xff0c…

ASP.NET Core真实管道详解[1]

ASP.NET Core管道虽然在结构组成上显得非常简单&#xff0c;但是在具体实现上却涉及到太多的对象&#xff0c;所以我们在 《ASP.NET Core管道深度剖析[共4篇]》 中围绕着一个经过极度简化的模拟管道讲述了真实管道构建的方式以及处理HTTP请求的流程。在这个系列 中&#xff0c;…

Wayland 显示服务器,wayland 1.8.0 发布,显示服务器

Wayland 1.8.0 发布&#xff0c;该版本现已提供下载&#xff1a; wayland-1.8.0.tar.xz&#xff1a;该版本与 RC2 相比的变化&#xff1a;publish-doc: Add script for publishing docs to the websiteconfigure.ac: bump to version 1.8.0 for the official releasescanner: d…

华为把服务器虚拟底层锁了,华为全面关闭解码锁服务:马上升级到很吓人的技术!...

原标题&#xff1a;华为全面关闭解码锁服务&#xff1a;马上升级到很吓人的技术&#xff01;华为余承东被网友称之为余大嘴&#xff0c;华为P20 Pro发布之后&#xff0c;余大嘴豪言华为P20的销量将超过2000万部&#xff0c;很就会推出一项“很吓人的技术”&#xff0c;没想到仅…

ASP.NET Core真实管道详解[2]:Server是如何完成针对请求的监听、接收与响应的【上】

Server是ASP .NET Core管道的第一个节点&#xff0c;负责完整请求的监听和接收&#xff0c;最终对请求的响应同样也由它完成。Server是我们对所有实现了IServer接口的所有类型以及对应对象的统称&#xff0c;如下面的代码片段所示&#xff0c;这个接口具有一个只读属性Features…

pos机未能连接服务器,pos 机链接不了服务器

pos 机链接不了服务器 内容精选换一换在本章节中&#xff0c;您将运行已部署好的游戏&#xff0c;登录游戏客户端。已准备好Windows机器&#xff0c;硬盘至少20G&#xff0c;且必须安装有显卡。服务器地址&#xff1a;节点的弹性IP地址&#xff0c;请登录CCE控制台&#xff0c;…

ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求

之所以称ASP.NET Core是一个Web开发平台&#xff0c;源于它具有一个极具扩展性的请求处理管道&#xff0c;我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求。ASP. NET Core应用的很多特性&#xff0c;比如路由、认证、会话、缓存等&#xff0c;也同时定制消息处理管…

emui消息推送服务器,别再抱怨,这次或许真的轮到你了,EMUI9.1推送进度再次更新...

最近几年里&#xff0c;华为在系统方面下的功夫可谓是大家有目共睹的。以往大家在使用华为EMUI操作系统的时候&#xff0c;或许会感觉到卡顿、应用启动时间过长、运行不流畅以及UI界面毫无亮点可言等&#xff0c;但那已经是过去的EMUI系统了&#xff0c;今日的EMUI系统已与往日…

ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

从《ASP.NET Core管道深度剖析&#xff08;1&#xff09;&#xff1a;采用管道处理HTTP请求》我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成&#xff0c;所以从总体设计来讲是非常简单的&#xff0c;但是就具体的实现来说&#xff0c;由于其中涉及很多对…

ASP.NET Core管道深度剖析(3):管道是如何处理HTTP请求的?

我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成&#xff0c;所以从总体设计来讲是非常简单的&#xff0c;但是就具体的实现来说&#xff0c;由于其中涉及很多对象的交互&#xff0c;我想很少人能够地把它弄清楚。为了让读者朋友们能够更加容易地理解管道…

ASP.NET Core管道深度剖析(4):管道是如何建立起来的?

在《管道是如何处理HTTP请求的&#xff1f;》中&#xff0c;我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍&#xff0c;接下来我们需要了解的是这样一个管道是如何被构建起来的。这样一个管道由一个服务器和一个HttpApplication构成&#xff0c…