阅读目录html
任务并行库(Task Parallel Library)是BCL中的一个类库,极大地简化了并行编程,Parallel经常使用的方法有For/ForEach/Invoke三个静态方法。在C#中for/foreach循环使用十分广泛,若是迭代不依赖与上次迭代的结果时,把迭代放在 不一样的处理器上并行处理 将很大地提升运行效率,Parallel.For和Parallel.ForEach就是为这个目的而设计的。编程
看一个Parallel.For/ForEach的栗子:windows
static void Main(string[] args) { //Parallel.For 计算0到6的平方 Parallel.For(1, 6, 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(); }
执行结果以下:异步
计时器提供了一种 按期重复运行异步方法 的方式,当计时器到期后,系统从线程池中的线程上开启一个回调方法,把state做为参数,并开始运行。异步编程
Timer最经常使用的构造函数以下:函数
Timer(TimeCallback callback,object state,uint dueTime, uint period)
callback是一个返回值为void的委托,state为传入callback的参数,dueTime为第一次调用前的时间,period为两次调用的时间间隔优化
一个栗子:ui
1 class Program 2 { 3 int count = 0; 4 void Run(object state) 5 { 6 Console.WriteLine("{0},已经调用了{1}次了", state, ++count); 7 } 8 static void Main(string[] args) 9 { 10 Program p = new Program(); 11 //2000毫秒后开始调用,每次间隔1000毫秒 12 Timer timer = new Timer(p.Run, "hello", 2000, 1000); 13 Console.WriteLine("Timer start"); 14 15 Console.ReadLine(); 16 } 17 }
执行结果:
委托执行异步是早期执行异步的一种方式,特别是早几年进行网络编程时用的比较多。如今咱们彻底可使用更优秀的其余异步编程模式去替代它。有时候咱们会查看早期的代码,咱们在这里简单介绍下委托执行异步的方法。使用委托执行异步,使用的是引用方法,若是一个委托对象在调用列表中只有一个方法(这个方法就是引用方法),它就能够异步执行这个方法。委托类有两个方法 BeginIvoke和EndInvoke 。
BeginInvoke :执行BeginInvoke方法时,会线程池中获取一个独立线程来执行引用方法,并当即返回一个实现IAsyncResult接口的对象的(该对象包含了线程池中线程运行异步方法的状态),调用线程不阻塞,而引用方法在线程池的线程中并行执行。
EndInvoke : 获取异步方法调用返回的值,并释放资源,该方法把异步方法的返回值做为本身的返回值。
委托执行异步编程的3种模式:
等待一直到完成(wait-until-done):在发起了异步方法,原始线程执行到EndInvoke时就中断而且等异步方法完成完成后再继续。
轮询(polling):原始线程按期检查发起的线程是否完成(经过IAsyncResult.IsCompleted属性判断),若是没有则继续进行原始线程中的任务。
回调(callback):原始线程一直执行,无需等待或检查发起的线程是否完成,在发起的线程中的引用方法完成以后,发起线程会调用回调方法,由回调方法在调用EndInvoke以前处理异步方法的结果。
原始线程执行到EndInvoke,若是异步任务没有完成就一直等待
1 delegate int MyDel(int first,int second);//委托声明 2 class Program 3 { 4 static int Sum(int x, int y) 5 { 6 Thread.Sleep(1000); 7 return x + y; 8 } 9 static void Main(string[] args) 10 { 11 MyDel del = Sum; 12 //调用异步操做(第三个参数是回调函数,第四个参数是额外的值) 13 IAsyncResult iar = del.BeginInvoke(3, 5, null, null); 14 15 //doSomehing... 16 17 //执行EndInvoke,若是引用方法Sum没有执行完成,主线程就等待其完成 18 int result = del.EndInvoke(iar); 19 Console.WriteLine(result); 20 } 21 }
按期查询任务是否完成:
1 delegate int MyDel(int first,int second);//委托声明 2 class Program 3 { 4 static int Sum(int x, int y) 5 { 6 Thread.Sleep(1000); 7 return x + y; 8 } 9 static void Main(string[] args) 10 { 11 MyDel del = Sum; 12 IAsyncResult iar = del.BeginInvoke(3, 5, null, null); 13 14 //经过iar.IsCompleted按期查询完成状态 15 while (!iar.IsCompleted)//IsCompleted表示调用的异步操做是否完成 16 { 17 //doSomething 18 Thread.Sleep(300); 19 Console.WriteLine("no done"); 20 } 21 int result = del.EndInvoke(iar); 22 Console.WriteLine(result); 23 Console.ReadKey(); 24 } 25 }
原始线程执行委托的BeginInvoke后就无论新线程的事了,委托中的引用方法执行完成后,在回调函数中获取结果并处理,执行委托的EndInvoke方法
1 delegate int MyDel(int first,int second);//委托声明 2 class Program 3 { 4 static int Sum(int x, int y) 5 { 6 Thread.Sleep(1000); 7 return x + y; 8 } 9 10 //回调方法的签名和返回值类型必须和AsyncCallBack委托类型一致 11 //输入参数为IAsyncResult,返回值是Void类型 12 static void CallWhenDone(IAsyncResult iar){ 13 AsyncResult ar = (AsyncResult)iar; 14 MyDel del = (MyDel)ar.AsyncDelegate; 15 int result = del.EndInvoke(iar); 16 Console.WriteLine("回调函数执行EndInvoke"); 17 Console.WriteLine("result:{0}", result); 18 Console.WriteLine("回调函数完成"); 19 } 20 21 static void Main(string[] args) 22 { 23 MyDel del = Sum; 24 //执行BeginInvoke方法后原始线程就不用管了,在自定义的回调函数(CallWhenDone)中执行EndInvoke方法 25 IAsyncResult iar = del.BeginInvoke(3, 5, CallWhenDone, null); 26 Console.WriteLine("开启新线程,异步任务完成后执行回调函数"); 27 //doSomething 28 Console.WriteLine("回调执行不阻塞原始线程"); 29 Console.ReadKey(); 30 } 31 }
执行结果:
还有一些其余的异步编程模式如BackgroundWorker等,这里再也不过多介绍。
咱们使用多线程时有时会遇到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(1000); 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)死循环,定位结束。
内存爆满也是异常遇到的问题,如大量拼接字符串会占用较大的内存,看下边的一个栗子,程序代码以下:
class Program { static void Main(string[] args) { Console.WriteLine("开始执行.."); GetBigString(); Console.ReadKey(); } //大字符串拼接 static void GetBigString() { String str = ""; for (int i = 0; i < 10000000; i++) { str+=$"hello{i}"; } Console.WriteLine(str); } }
内存爆满最多见缘由是大量建立某个类型的变量,问题定位方法和上边定位cpu占用高的定位差很少。首先生成dump文件,而后用Windbg打开,加载符号和sos库,而后执行 !dumpheap –stat 查看各个类型的数量和尺寸,咱们看到string类型数量和占用的资源不少:
经过 !DumpHeap /d -mt 00007ff8878c74c0 查看当前的方法表,以下:
点开一个地址,具体内容以下:
经过字符串内容是【hello0hello1...】和string类型数量多、尺寸大,咱们再去在代码中查找很容易定位到问题代码。
小结:Windbg能够查看clr级别内容,在开发中对咱们优化代码和异常定位有不错的帮助,这里只是简单介绍Windbg的基本用法,有兴趣的小伙伴能够研究下官方教程。