线程池简述+两种传统的异步编程模式

1.线程池简述

  首先我们要明确一点,编程中讲的线程与平时我们形容CPU几核几线程中的线程是不一样的,CPU线程是指逻辑处理器,比如4核8线程,讲的是这个cpu有8个逻辑处理器,可以同时处理8个线程。我们编程中讲的线程在计算机中可以有许多许多,如下图所示,这些线程并不是都在执行状态,他们平时大部分都是休眠状态,只有进程去调用他们时,他们才是激活状态。线程通过他们的ThreadState(线程状态)属性告诉CPU,它们是否需要被CPU去执行。比如有2000个线程,其中有20个线程的线程状态属性为“待执行”,那么CPU的逻辑处理器就会在空闲时根据线程的优先级去执行线程(4核8线程的CPU最多同时执行8个线程),正在执行的线程状态属性会被改为“正在执行”,当该线程执行结束后,其线程状态属性会被改为“休眠”,此时CPU就不会再理他们。

  • 什么是C#线程池呢?

  顾名思义,线程池就是放线程的池子,我们在运行任意.NET程序时,都会在CLR(你可以把他理解为软件后台)生成一个线程池,池内已经new出来了很多的Thread实例,我们在需要型线程的时候不用自己new,直接从池子里拿现成的Thread实例即可,用完后这个Thread实例会被自动还回线程池!线程池中的线程对象数量与我们计算机有关,具体数字我忘了,反正是CPU核心越多,逻辑处理器越多,那么线程池的线程就越多,我们一般不用管池内有多少个线程(一般是足够你用的),即使线程池的线程都在被占用状态,此时你再从线程池拿线程时,线程池也会自动new新增一个线程给你。

  • 为什么要使用C#线程池呢?

  因为new一个Thread是比较耗费资源并且执行较慢的行为,比如我们在一个1000次的循环中,每个循环都要new出一个Thread进行某些任务的处理,会使得任务执行缓慢,并且计算机内存蹭蹭上涨。我们不如直接在每次循环中从线程池获取一个线程,用完再放回去,这样的处理不仅速度快,对内存也没有任何影响。

2.线程池的使用(简单讲解)

  因为线程池在.NET4.0后新出的Task类及Async与await关键字出现后就不怎么用了,这里仅仅简单讲一讲线程池的用法。

  直接看代码:

        //创建一个线程执行的方法
public static void DoSth(object obj)
{
//输出当前执行线程的ID
Console.WriteLine((string)obj+Thread.CurrentThread.ManagedThreadId);
Thread.Sleep();//线程睡眠5秒
} static void Main(string[] args)
{
for (int i = ; i < ; i++)
{
//-----------------非简写方式-----------------
//WaitCallback是一个委托(有一个Object类型参数,无返回值)
WaitCallback callBack = new WaitCallback(DoSth);
//QueueUserWorkItem只支持WaitCallback作为参数,第二个参数是传入委托方法的参数
ThreadPool.QueueUserWorkItem(callBack, "abc"); //-----------------lambda简写方式-----------------
ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
{
//输出当前执行线程的ID
Console.WriteLine((string)obj + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep();//线程睡眠5秒
}),"abc");
}
}

  这里补充讲一下ThreadPool.QueueUserWorkItem方法,这个方法从作用上讲是从线程池获取一个新线程去执行一个委托方法。但是为什么它的方法名是QueueUserWorkItem而非GetValueableThread这样的名称呢?因为QueueUserWorkItem的实质其实是将委托方法传入线程池的一个任务队列中,线程池中的空闲线程负责去对任务队列中的线程进行执行,这才是它实质的运行逻辑。

  注意:ThreadPool.QueueUserWorkItem只能接受“有且只有一个Object类型,且无返回值的委托方法”。

3.异步编程简介

  同步编程:我们平时不用多线程的时候基本就是同步编程模式,代码从上到下依次执行。

  关于什么是异步编程模式,首先我们看一段代码:

        //声明一个执行一次耗费5分钟的方法
public static void Spend5Min()
{
for(int i=;i<;i++)
{
Thread.Sleep();
}
}
     //主方法
static void Main(string[] args)
{
Thread t1 = new Thread(Spend5Min);
t1.Start();//将Spend5Min()这个方法交给t1线程执行 Spend5Min();//主线程去执行Spend5Min()方法
}

  这段代码会怎么运行呢?

  首先主线程会将Spend5Min这个方法交给t1线程去运行,然后自己也开始运行Spend5Min这个方法。这时候,主线程与t1线程基本会同时执行Spend5Min方法。

  上面这种模式就是异步编程模式,异步编程的实质就是代码并非是从上到下依次执行的,而是在代码中间产生一个新线程分支,去执行新任务,主线程只负责将任务交给他,然后就不管它,继续往下执行。(而不是等t1线程把Spend5Min方法执行完毕后,主线程再开始执行Spen5Min)。

  这里有一个的误区,许多人觉得只要用了多线程就是异步编程,请看下面的代码:

        static void Main(string[] args)
{
Thread t1 = new Thread(Spend5Min);
t1.Start();//将Spend5Min()这个方法交给t1线程执行
t1.Join();//让主线程等待t1线程执行完毕再继续执行。

       Spend5Min();//主线程去执行Spend5Min()方法
}

  因为t1.Join方法,让主线程再此处会等待t1线程执行完毕再继续执行,这种编程模式其实依旧是同步编程模式,因为它依旧是从上到下依次执行的,上面这段代码可以说等同于下面这段代码。

        static void Main(string[] args)
{
Spend5Min();
Spend5Min();
}

4.传统的异步编程模式APM

  C# .NET最早出现的异步编程模式被称为APM(Asynchronous Programming Model)。这种模式主要由一对Begin/End开头的方法组成。BeginXXX方法用于异步启动一个耗时任务,EndXXXEndXXX用来处理BeginXXX所返回的值(IAsyncResult对象)。BeginXXX方法和EndXXX方法之间的信息通过一个IAsyncResult对象来传递,IAsyncResult 对象是异步的核心,简单的说,他是存储异步返回值+异步状态信息的一个接口,也可以用它来结束当前异步。

  .NET中一个典型的例子是System.Net命名空间中的HttpWebRequest类里的BeginGetResponse和EndGetResponse这对方法:

IAsyncResult BeginGetResponse(AsyncCallback callback, object state)

  上面的BeginGetResponse用来开启一个异步方法,下面这个方法用于处理上面异步方法返回的值,只有执行完了EndXXX,一个完整的异步操作才算完成(EndXXX一般写在Beginxxxx的回调函数中)。

WebResponse EndGetResponse(IAsyncResult asyncResult);

  注意: BeginInvoke和EndInvoke必须成对调用.即使不需要返回值,但EndInvoke还是必须调用,否则可能会造成内存泄漏。

  APM使用简单明了,虽然代码量稍多,但也在合理范围之内。APM两个最大的缺点是不支持进度报告以及不能方便的“取消”。
  示例:   

  (1)同步调用异步方法

  下面代码介绍了APM异步方法的错误用法,虽然使用了异步方法,但是其效果依旧是同步模式,所以称下面的代码是同步方式调用异步方法。

    public class Program
{
public delegate int AddHandler(int a, int b); public static int Add(int a, int b)
{
Thread.Sleep();
return a+b;
Console.WriteLine("异步方法执行完毕");
}
static void Main()
{
AddHandler handler = new AddHandler(Add); //BeginInvoke: 委托(delegate)的一个异步方法的开始
//第三个函数为回调函数,BeginInvoke完成后自动执行
IAsyncResult result = handler.BeginInvoke(,,null,null);
Console.WriteLine("在前面没执行完前我这就执行完了");//在异步方法还没执行完之前,此句代码就会被执行 //返回异步操作结果()
//因为result还没有被异步方法返回,主线程代码会卡在这个地方,直到异步方法把result返回(这就导致与同步代码一样了)
Console.WriteLine(handler.EndInvoke(result));
Console.ReadLine();
}
}

  代码解释:handler.BeginInvoke仅仅只负责开始异步执行委托方法,并返回当前异步result对象。只有主动执行handler.EndInvoke(异步result)才可获取到方法return中的结果。
  代码效果:可以看到,主线程并没有等待,而是直接向下运行了。但是问题依然存在,当主线程运行到EndInvoke时,如果这时BeginInvoke没有执行结束(result还没被算出来),这时为了等待调用结果,主线程依旧会被阻塞。

  (2)正确使用APM异步模式

  思路:将handler.EndInvoke放在handler.BeginInvoke的回调函数中执行,这样当BeginInvoke执行完毕后,后台线程继续执行回调函数(包括handler.EndInvoke方法)直接输出结果,不会阻塞主线程。

    public class Program
{
public delegate int AddHandler(int a, int b); public static int Add(int a, int b)
{
Thread.Sleep();
return a+b;
Console.WriteLine("异步方法执行完毕");
}
static void Main()
{
AddHandler handler = new AddHandler(Add); //第三个函数为回调函数,BeginInvoke完成后自动执行
//第四个函数定义异步执行result完成后的状态
IAsyncResult result = handler.BeginInvoke(,,new AsyncCallback(MyCallback),"AsycState:OK");
Console.WriteLine("在前面没执行完前我这就执行完了"); Console.ReadLine();
}
//异步回调:异步中执行的回调函数
static void MyCallback(IAsyncResult result)
{
//result 是“加法Add()方法”的返回值
//AsyncResult 是IAsyncResult接口的一个实现类,要引用命名空间:System.Runtime.Remoting.Messaging
//AsyncDelegate 属性可以强制转换为用户定义的委托的实际类。
AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
Console.WriteLine(handler.EndInvoke(result));
Console.WriteLine(result.AsyncState);
}
}

  补充:因为委托的BeginInvoke中第4个参数可以放入任意对象,一般用于包含关于异步操作的信息,所以为了简化回调函数,我们可以直接将委托对象传递到回调函数:

IAsyncResult result = handler.BeginInvoke(,,new AsyncCallback(AddComplete),AddHandler);

  这时result.AsyncState就装着AddHandler委托对象了,回调函数可简化为:

        static void AddComplete(IAsyncResult result)
{
AddHandler handler = (AddHandler)result.AsyncState;
Console.WriteLine(handler.EndInvoke(result));
。。。。。
}

  补充:如何在普通方法中创建回调函数?代码如下:

        public void Method(参数1,参数2,Action<string> CallBackHandler)
{
//正常执行
string result = ...;//得到结果
//将结果传入回调函数中
CallBackHandler.Invoke(result);
}

5.传统的异步编程模式EAP

  在C# .NET第二个版本中,增加了一种新的异步编程模型EAP(Event-based Asynchronous Pattern),EAP模式的异步代码中,典型特征是一个以"Async"结尾的"方法"和以"Completed"结尾的"事件"。XXXCompleted事件将在异步处理完成时被触发,在事件的处理函数中可以操作异步方法的结果。往往在EAP代码中还会存在名为CancelAsync的方法用来取消异步操作,以及一个ProgressChenged结尾的事件用来汇报操作进度。通过这种方式支持取消和进度汇报也是EAP比APM更有优势的地方。EAP中取消机制没有可延续性,并且不是很通用。

  .NET2.0中新增的BackgroundWorker可以看作EAP模式的一个例子。另一个使用EAP的例子是被HttpClient所取代的WebClient类(新代码应该使用HttpClient而不是WebClient)。WebClient类中通过DownloadStringAsync方法开启一个异步任务,并有DownloadStringCompleted事件供设置回调函数,还能通过CancelAsync方法取消异步任务。

  因为APM与EAP异步编程模式目前在新代码中基本不用了,所以这里就随便讲讲,后续博客中将详细的讲解对基于Task及Async与await关键字的TAP异步模式。

【C#多线程】2.线程池简述+两种传统的异步模式的更多相关文章

  1. Android 应用开发 之通过AsyncTask与ThreadPool(线程池)两种方式异步加载大量数据的分析与对比--转载

     在加载大量数据的时候,经常会用到异步加载,所谓异步加载,就是把耗时的工作放到子线程里执行,当数据加载完毕的时候再到主线程进行UI刷新.在数据量非常大的情况下,我们通常会使用两种技术来进行异步加载,一 ...

  2. java线程池与五种常用线程池策略使用与解析

    背景:面试中会要求对5中线程池作分析.所以要熟知线程池的运行细节,如CachedThreadPool会引发oom吗? java线程池与五种常用线程池策略使用与解析 可选择的阻塞队列BlockingQu ...

  3. Qt多线程-QThreadPool线程池与QRunnable

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt多线程-QThreadPool线程池与QRunnable     本文地址:https:/ ...

  4. Java基础教程:多线程基础——线程池

    Java基础教程:多线程基础——线程池 线程池 在正常负载的情况瞎,通过为每一个请求创建一个新的线程来提供服务,从而实现更高的响应性. new Thread(runnable).start() 在生产 ...

  5. Qt中的多线程与线程池浅析+实例

    1. Qt中的多线程与线程池 今天学习了Qt中的多线程和线程池,特写这篇博客来记录一下 2. 多线程 2.1 线程类 QThread Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一 ...

  6. Java多线程与线程池技术

    一.序言 Java多线程编程线程池被广泛使用,甚至成为了标配. 线程池本质是池化技术的应用,和连接池类似,创建连接与关闭连接属于耗时操作,创建线程与销毁线程也属于重操作,为了提高效率,先提前创建好一批 ...

  7. Java 多线程:线程池

    Java 多线程:线程池 作者:Grey 原文地址: 博客园:Java 多线程:线程池 CSDN:Java 多线程:线程池 工作原理 线程池内部是通过队列结合线程实现的,当我们利用线程池执行任务时: ...

  8. C#多线程之线程池篇3

    在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...

  9. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

随机推荐

  1. CMD的最佳“代替品”

    让CMD成为历史 Windows用户大多都使用过"cmd",cmd被称为"阉割版"的DOS系统~ 很多用户除此之外,还喜欢Linux命令行~但是CMD的命令和L ...

  2. Ansible配置详解

    目录 Ansible配置详解 参考 配置优先级 配置参数说明 Ansible配置详解

  3. cat命令显示文件指定行

    cat filename | tail -n 100 显示文件最后100行 cat filename | head -n 100 显示文件前面100行 cat filename | tail -n + ...

  4. inkscape 无法打开文档属性

    从文件->文档属性 点击了无反应 参考https://bugs.launchpad.net/inkscape/+bug/1664031 其实不是无反应,只是因为我们自己的某些操作,让文档属性这个 ...

  5. 11g bug event 'cursor: mutex S'-引发的CPU冲高问题

    问题背景:客户反应数据库服务器CPU占用过高 1> 确认问题根源登录客户DB服务器: top 查看当前负载 (几乎100%)top - 10:47:55 up 29 days, 21:51, 3 ...

  6. 继续学习freertos消息队列

    写在前面:杰杰这个月很忙~所以并没有时间更新,现在健身房闭馆装修,晚上有空就更新一下!其实在公众号没更新的这段日子,每天都有兄弟在来关注我的公众号,这让我受宠若惊,在这里谢谢大家的支持啦!!谢谢^ 在 ...

  7. python程序调用C/C++代码

    这篇用来记录在些模拟Canoe生成CAN数据桢工具时遇到的问题, 生成CAN数据桢,主要分为两个关注点: 1.如何从can信号名获取到can信号的ID长度以及信号的起始位,并将信号值按照一定的规则填写 ...

  8. .NET LINQ分析AWS ELB日志避免996

    前言 小明是个单纯的.NET开发,一天大哥叫住他,安排了一项任务: "小明,分析一下我们超牛逼网站上个月的所有AWS ELB流量日志,这些日志保存在AWS S3上,你分析下,看哪个API的响 ...

  9. Java的Object类

    (1)Object是类层次结构的根类,所有的类都直接或者间接的继承自Object类. (2)Object类的构造方法有一个,并且是无参构造 这其实就是理解当时我们说过,子类构造方法默认访问父类的构造是 ...

  10. Java学习之面试题整理

    1,java 基本数据类型有几种?哪几种?(面试题) 8种 byte short int long float double char boolean 2,int类型是几个字节?(面试题) 4字节 3 ...