C#多线程和异步(三)——一些异步编程模式
一、任务并行库
任务并行库(Task Parallel Library)是BCL中的一个类库,极大地简化了并行编程,Parallel常用的方法有For/ForEach/Invoke三个静态方法。在C#中for/foreach循环使用十分普遍,如果迭代不依赖与上次迭代的结果时,把迭代放在 不同的处理器上并行处理 将很大地提高运行效率,Parallel.For和Parallel.ForEach就是为这个目的而设计的。
看一个Parallel.For/ForEach的栗子:
static void Main(string[] args)
{
//Parallel.For 计算0到6的平方
Parallel.For(, , i =>
{
Console.WriteLine($"{i}的平方是{i*i}");
}); //Parallel.ForEach 计算每个字符串的长度
string[] strs = { "We", "hold", "these", "truths" };
Parallel.ForEach(strs, i => Console.WriteLine($"{i}有{i.Length}个字节"));
Console.ReadKey();
}
运行结果:

如果我们想并行执行多个任务,可以使用 Parallel.Invoke(Action[] actions) 方法,看一个栗子:
static void Main(string[] args)
{
Parallel.Invoke(
() => { Console.WriteLine($"并行执行任务1,线程Id为{Thread.CurrentThread.ManagedThreadId}"); },
() => { Console.WriteLine($"并行执行任务2,线程Id为{Thread.CurrentThread.ManagedThreadId}"); }
);
Console.ReadKey();
}
执行结果如下:

二、计时器(Timer)
计时器提供了一种 定期重复运行异步方法 的方式,当计时器到期后,系统从线程池中的线程上开启一个回调方法,把state作为参数,并开始运行。
Timer最常用的构造函数如下:
Timer(TimeCallback callback,object state,uint dueTime, uint period)
callback是一个返回值为void的委托,state为传入callback的参数,dueTime为第一次调用前的时间,period为两次调用的时间间隔
一个栗子:
class Program
{
int count = ;
void Run(object state)
{
Console.WriteLine("{0},已经调用了{1}次了", state, ++count);
}
static void Main(string[] args)
{
Program p = new Program();
//2000毫秒后开始调用,每次间隔1000毫秒
Timer timer = new Timer(p.Run, "hello", , );
Console.WriteLine("Timer start"); Console.ReadLine();
}
}
执行结果:

三、委托执行异步
委托执行异步是早期执行异步的一种方式,特别是早几年进行网络编程时用的比较多。现在我们完全可以使用更优秀的其他异步编程模式去替代它。有时候我们会查看早期的代码,我们在这里简单介绍下委托执行异步的方法。使用委托执行异步,使用的是引用方法,如果一个委托对象在调用列表中只有一个方法(这个方法就是引用方法),它就可以异步执行这个方法。委托类有两个方法 BeginIvoke和EndInvoke 。
BeginInvoke :执行BeginInvoke方法时,会线程池中获取一个独立线程来执行引用方法,并立即返回一个实现IAsyncResult接口的对象的(该对象包含了线程池中线程运行异步方法的状态),调用线程不阻塞,而引用方法在线程池的线程中并行执行。
EndInvoke : 获取异步方法调用返回的值,并释放资源,该方法把异步方法的返回值作为自己的返回值。
委托执行异步编程的3种模式:
等待一直到完成(wait-until-done):在发起了异步方法,原始线程执行到EndInvoke时就中断并且等异步方法完成完成后再继续。
轮询(polling):原始线程定期检查发起的线程是否完成(通过IAsyncResult.IsCompleted属性判断),如果没有则继续进行原始线程中的任务。
回调(callback):原始线程一直执行,无需等待或检查发起的线程是否完成,在发起的线程中的引用方法完成之后,发起线程会调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结果。
3.1 等待一直到完成模式
原始线程执行到EndInvoke,如果异步任务没有完成就一直等待
delegate int MyDel(int first,int second);//委托声明
class Program
{
static int Sum(int x, int y)
{
Thread.Sleep();
return x + y;
}
static void Main(string[] args)
{
MyDel del = Sum;
//调用异步操作(第三个参数是回调函数,第四个参数是额外的值)
IAsyncResult iar = del.BeginInvoke(, , null, null); //doSomehing... //执行EndInvoke,如果引用方法Sum没有执行完成,主线程就等待其完成
int result = del.EndInvoke(iar);
Console.WriteLine(result);
}
}
3.2 轮询模式
定期查询任务是否完成:
delegate int MyDel(int first,int second);//委托声明
class Program
{
static int Sum(int x, int y)
{
Thread.Sleep();
return x + y;
}
static void Main(string[] args)
{
MyDel del = Sum;
IAsyncResult iar = del.BeginInvoke(, , null, null); //通过iar.IsCompleted定期查询完成状态
while (!iar.IsCompleted)//IsCompleted表示调用的异步操作是否完成
{
//doSomething
Thread.Sleep();
Console.WriteLine("no done");
}
int result = del.EndInvoke(iar);
Console.WriteLine(result);
Console.ReadKey();
}
}

3.3 回调模式
原始线程执行委托的BeginInvoke后就不管新线程的事了,委托中的引用方法执行完成后,在回调函数中获取结果并处理,执行委托的EndInvoke方法
delegate int MyDel(int first,int second);//委托声明
class Program
{
static int Sum(int x, int y)
{
Thread.Sleep();
return x + y;
} //回调方法的签名和返回值类型必须和AsyncCallBack委托类型一致
//输入参数为IAsyncResult,返回值是Void类型
static void CallWhenDone(IAsyncResult iar){
AsyncResult ar = (AsyncResult)iar;
MyDel del = (MyDel)ar.AsyncDelegate;
int result = del.EndInvoke(iar);
Console.WriteLine("回调函数执行EndInvoke");
Console.WriteLine("result:{0}", result);
Console.WriteLine("回调函数完成");
} static void Main(string[] args)
{
MyDel del = Sum;
//执行BeginInvoke方法后原始线程就不用管了,在自定义的回调函数(CallWhenDone)中执行EndInvoke方法
IAsyncResult iar = del.BeginInvoke(, , CallWhenDone, null);
Console.WriteLine("开启新线程,异步任务完成后执行回调函数");
//doSomething
Console.WriteLine("回调执行不阻塞原始线程");
Console.ReadKey();
}
}
执行结果:

还有一些其他的异步编程模式如BackgroundWorker等,这里不再过多介绍。
一点补充(Windbg)
1 cpu占用过高
我们使用多线程时有时会遇到cpu占用过高、内存爆满的情况,快速定位异常线程是多线程开发中必须熟悉的技能。cpu占用过高一般是由死循环造成的,看下边一个简单的栗子,Run方法内部有死循环,程序运行后会 占用大量的cpu资源:
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
Run();
Run2();
Console.ReadKey();
}
//死循环,会造成cpu内存占用过高
static void Run()
{
Thread th = new Thread(() =>
{
while (true)
{
Console.WriteLine("hello windbg");
}
});
th.Start();
}
//不会占用太高的cpu资源
static void Run2()
{
Thread th = new Thread(() =>
{
while (true)
{
Thread.Sleep();
Console.WriteLine("hello windbg2");
}
});
th.Start();
}
}
}
程序运行后cpu资源占用过高,怎么去定位呢?这里采用Windbg简单演示cpu占用过高的异常定位,下载地址:Windbg下载。安装完成后,界面如下所示:

1.生成Dump文件
这里MyApp生成为x64位的Release版本,点击MyApp.exe文件运行,打开【任务管理器】,找到MyApp,右键选择【创建转储文件】即可生成dump文件。

2.Windbg分析dump文件
打开Windbg,选择【文件】->【Open dump file】->找到上一步生成的dump文件即可。

执行以下命令加载符号和sos库
.sympath SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols
.reload
.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.DLL
通过命令 !threads 查看线程:

死循环会长期占有cpu,通过 !runaway 查看各个线程的运行时间:

我们看到 4eac线程的运行时间最长,通过命令 ~~[4eac] ; !clrstack 查看线程堆栈信息:
我们看到异常定位在MyApp的Program类的第24行,查看我们的代码,找到这个位置,发现这里是一个while(true)死循环,定位结束。

2 内存爆满
内存爆满也是异常遇到的问题,如大量拼接字符串会占用较大的内存,看下边的一个栗子,程序代码如下:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("开始执行..");
GetBigString();
Console.ReadKey();
}
//大字符串拼接
static void GetBigString()
{
String str = "";
for (int i = ; i < ; i++)
{
str+=$"hello{i}";
}
Console.WriteLine(str);
}
}
内存爆满最常见原因是大量创建某个类型的变量,问题定位方法和上边定位cpu占用高的定位差不多。首先生成dump文件,然后用Windbg打开,加载符号和sos库,然后执行 !dumpheap –stat 查看各个类型的数量和尺寸,我们看到string类型数量和占用的资源很多:

通过 !DumpHeap /d -mt 00007ff8878c74c0 查看当前的方法表,如下:

点开一个地址,具体内容如下:

通过字符串内容是【hello0hello1...】和string类型数量多、尺寸大,我们再去在代码中查找很容易定位到问题代码。
小结:Windbg可以查看clr级别内容,在开发中对我们优化代码和异常定位有不错的帮助,这里只是简单介绍Windbg的基本用法,有兴趣的小伙伴可以研究下官方教程。
C#多线程和异步(三)——一些异步编程模式的更多相关文章
- 多线程:多线程设计模式(三):Master-Worker模式
Master-Worker模式是常用的并行模式之一,它的核心思想是,系统有两个进程协作工作:Master进程,负责接收和分配任务:Worker进程,负责处理子任务.当Worker进程将子任务处理完成后 ...
- 多线程设计模式(三):Master-Worker模式
Master-Worker模式是常用的并行模式之一,它的核心思想是,系统有两个进程协作工作:Master进程,负责接收和分配任务:Worker进程,负责处理子任务.当Worker进程将子任务处理完成后 ...
- iOS 网络编程模式总结
IOS 可以采用三类api 接口进行网络编程,根据抽象层次从低到高分别为socket方式.stream方式.url 方式. 一 .socket 方式 IOS 提供的socket 方式的网络编程接口为C ...
- [.net 多线程]异步编程模式
.NET中的异步编程 - EAP/APM 从.NET 4.5开始,支持的三种异步编程模式: 基于事件的异步编程设计模式 (EAP,Event-based Asynchronous Pattern) 异 ...
- C#中的异步调用及异步设计模式(三)——基于事件的异步模式
四.基于事件的异步模式(设计层面) 基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合.该异步模式具有以下优点: · ...
- .Net Core自实现CLR异步编程模式(Asynchronous programming patterns)
最近在看一个线程框架,对.Net的异步编程模型很感兴趣,所以在这里实现CLR定义的异步编程模型,在CLR里有三种异步模式如下,如果不了解的可以详细看MSDN 文档Asynchronous progra ...
- .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)
本文内容 异步编程类型 异步编程模型(APM) 参考资料 首先澄清,异步编程模式(Asynchronous Programming Patterns)与异步编程模型(Asynchronous Prog ...
- 二、基于事件的异步编程模式(EAP)
一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进 ...
- 基于任务的异步编程模式,Task-based Asynchronous Pattern
术语: APM 异步编程模型,Asynchronous Programming Model,其中异步操作由一对 Begin/End 方法(如 FileStream.BeginRea ...
随机推荐
- 什么是Consul
什么是Consul Consul文档简要整理 什么是Consul? Consul是一个用来实现分布式系统的服务发现与配置的开源工具.他主要由多个组成部分: 服务发现:客户端通过Consul提供服务,类 ...
- Kitematic when login show Error:Tunning socket could not be established
https://cn.bing.com/search?q=tunning+socket+could+not+be+established&qs=n&form=QBRE&sp=- ...
- Java的JDK下Hashtable与HashMap的区别
时间角度: Hashtable * @since JDK1.0 ; HashMap* @since 1.2 基类与接口角度: public class Hashtable<K,V> e ...
- [转帖]Nginx安装及配置详解 From https://www.cnblogs.com/zhouxinfei/p/7862285.html
Nginx安装及配置详解 nginx概述 nginx是一款自由的.开源的.高性能的HTTP服务器和反向代理服务器:同时也是一个IMAP.POP3.SMTP代理服务器:nginx可以作为一个HTTP ...
- hive外部表
创建外部表.数据从HDFS获取 只是建立了链接,hdfs中的数据丢失,表中数据也丢失;hdfs数据增加,表中数据也增加 上传文件 创建外部表 删除文件 执行查询语句,发现少了
- hadoop故障及其应对
为更好了解各种故障,可以修改数据块的大小和提升NameNode的日志级别 <property> <name>dfs.block.size</name> <va ...
- 闭包自由变量引用对象的问题 http://bbs.pythontab.com/thread-4266-1-1.html
- Layui_2.x_上传插件使用
一.上传类 package com.ebd.application.common.utils; import java.awt.geom.AffineTransform; import java.aw ...
- centOS7 修改DNS
#显示当前网络连接 #nmcli connection show NAME UUID TYPE DEVICE eno1 5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03 802 ...
- BZOJ1419Red is good——概率DP
题目描述 桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付 出1美元.可以随时停止翻牌,在最优策略下平均能得到多少钱. 输入 一行输入两个数R,B ...