同步编程是对于单线程来讲的,就像咱们编写的控制台程序,以main方法为入口,顺序执行咱们编写的代码。数据库
异步编程是对于多线程来讲的,经过建立不一样线程来实现多个任务的并行执行。编程
多线程的意义在于一个应用程序中,有多个执行部分能够同时执行;对于比较耗时的操做(例如io,数据库操做),或者等待响应(如WCF通讯)的操做,能够单独开启后台线程来执行,这样主线程就不会阻塞,能够继续往下执行;等到后台线程执行完毕,再通知主线程,而后作出对应操做!数组
每个Windows进程都刚好包含一个用做程序入口点的主线程。进程的入口点建立的第一个线程被称为主线程。.Net执行程序(控制台、Windows Form、Wpf等)使用Main()方法做为程序入口点。当调用该方法时,主线程被建立。多线程
由主线程建立的线程,能够称为工做者线程,用来去执行某项具体的任务。并发
默认状况下,使用Thread.Start()方法建立的线程都是前台线程。前台线程能阻止应用程序的终结,只有全部的前台线程执行完毕,CLR才能关闭应用程序(即卸载承载的应用程序域)。前台线程也属于工做者线程。asp.net
后台线程不会影响应用程序的终结,当全部前台线程执行完毕后,后台线程不管是否执行完毕,都会被终结。通常后台线程用来作些可有可无的任务(好比邮箱每隔一段时间就去检查下邮件,天气应用每隔一段时间去更新天气)。后台线程也属于工做者线程。异步
在C#中开启新线程比较简单async
static void Main(string[] args) { Console.WriteLine("主线程开始!"); //建立前台工做线程 Thread t1 = new Thread(Task1); t1.Start(); //建立后台工做线程 Thread t2= new Thread(new ParameterizedThreadStart(Task2)); t2.IsBackground = true;//设置为后台线程 t2.Start("传参"); } private static void Task1() { Thread.Sleep(1000);//模拟耗时操做,睡眠1s Console.WriteLine("前台线程被调用!"); } private static void Task2(object data) { Thread.Sleep(2000);//模拟耗时操做,睡眠2s Console.WriteLine("后台线程被调用!" + data); }
试想一下,若是有大量的任务须要处理,例如网站后台对于HTTP请求的处理,那是否是要对每个请求建立一个后台线程呢?显然不合适,这会占用大量内存,并且频繁地建立的过程也会严重影响速度,那怎么办呢?线程池就是为了解决这一问题,把建立的线程存起来,造成一个线程池(里面有多个线程),当要处理任务时,若线程池中有空闲线程(前一个任务执行完成后,线程不会被回收,会被设置为空闲状态),则直接调用线程池中的线程执行(例asp.net处理机制中的Application对象)ide
线程池是为忽然大量爆发的线程设计的,经过有限的几个固定线程为大量的操做服务,减小了建立和销毁线程所需的时间,从而提升效率,这也是线程池的主要好处。异步编程
ThreadPool适用于并发运行若干个任务且运行时间不长且互不干扰的场景。
还有一点须要注意,经过线程池建立的任务是后台任务。
for (int i = 0; i < 100; i++) { //将方法排入队列以便执行,此方法在线程池中线程变的能够时执行 ThreadPool.QueueUserWorkItem(m => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); }); } Console.Read(); //虽然执行了100次,但并无建立100个线程。 //若是去掉最后一句Console.Read(),会发现程序仅输出【主线程开始!】就直接退出,从而肯定ThreadPool建立的线程都是后台线程。
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们可以正确、合理的使用公共资源。
以一个停车场是运做为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这是若是同时来了五辆车,看门人容许其中三辆不受阻碍的进入,而后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,若是又离开两辆,则又能够放入两辆,如此往复。 在这个停车场系统中,车位是公共资源,每辆车比如一个线程,看门人起的就是信号量的做用。 更进一步,信号量的特性以下:信号量是一个非负整数(车位数),全部经过它的线程(车辆)都会将该整数减一(经过它固然是为了使用资源),当该整数值为零时,全部试图经过它的线程都将处于等待状态。在信号量上咱们定义两种操做: Wait(等待) 和 Release(释放)。 当一个线程调用Wait等待)操做时,它要么经过而后将信号量减一,要么一自等下去,直到信号量大于一或超时。Release(释放)其实是在信号量上执行加操做,对应于车辆离开停车场,该操做之因此叫作“释放”是应为加操做其实是释放了由信号量守护的资源。
相似互斥锁,但它能够容许多个线程同时访问一个共享资源。经过使用一个计数器来控制对共享资源的访问,若是计数器大于0,就容许访问,若是等于0,就拒绝访问。计数器累计的是“许可证”的数目,为了访问某个资源。线程必须从信号量获取一个许可证。
Semaphore负责协调线程,能够限制对某一资源访问的线程数量
using System.Threading static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多只能有三个线程同时访问 static void Main(string[] args) { for (int i = 0; i < 10; i++) { new Thread(SemaphoreTest).Start(); } Console.Read(); } static void SemaphoreTest() { semLim.Wait(); Console.WriteLine("线程" + Thread.CurrentThread.ManagedThreadId.ToString() + "开始执行"); Thread.Sleep(2000); Console.WriteLine("线程" + Thread.CurrentThread.ManagedThreadId.ToString() + "执行完毕"); semLim.Release(); }
能够看到,刚开始只有三个线程在执行,当一个线程执行完毕并释放以后,才会有新的线程来执行方法!除了SemaphoreSlim类,还可使用Semaphore类,感受更加灵活
SemaphorePublic Semaphore(int initialCount,int maximumCount)
initialCount指信号量许可证的初始值,maximumCount为最大值.获取许可证使用WaitOne(),不须要时释放使用 public int Release()或者public int Release(int releaseCount)
public class MyThread { public Thread thrd; //建立一个可受权2个许可证的信号量,且初始值为2 static Semaphore sem = new Semaphore(2, 2); public MyThread(string name) { thrd = new Thread(this.run); thrd.Name = name; thrd.Start(); } void run() { Console.WriteLine(thrd.Name + "正在等待一个许可证……"); //申请一个许可证 sem.WaitOne(); Console.WriteLine(thrd.Name + "申请到许可证……"); for (int i = 0; i < 4; i++) { Console.WriteLine(thrd.Name + ": " + i); Thread.Sleep(1000); } Console.WriteLine(thrd.Name + " 释放许可证……"); //释放 sem.Release(); } } class Program { public static void Main() { mythread mythrd1 = new mythread("Thrd #1"); mythread mythrd2 = new mythread("Thrd #2"); mythread mythrd3 = new mythread("Thrd #3"); mythread mythrd4 = new mythread("Thrd #4"); mythrd1.thrd.Join(); mythrd2.thrd.Join(); mythrd3.thrd.Join(); mythrd4.thrd.Join(); } }
Task是.NET4.0加入的,跟线程池ThreadPool的功能相似,用Task开启新任务时,会从线程池中调用线程,而Thread每次实例化都会建立一个新的线程,简化了咱们进行异步编程的方式,而不用直接与线程和线程池打交道。用Task类能够轻松地在次线程中调用方法。
System.Threading.Tasks中的类型被称为任务并行库(TPL)。TPL使用CLR线程池(说明使用TPL建立的线程都是后台线程)自动将应用程序的工做动态分配到可用的CPU中。
Console.WriteLine("主线程启动"); //Task.Run启动一个线程 //Task启动的是后台线程,要在主线程中等待后台线程执行完毕,能够调用Wait方法 //Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task启动"); }); Task task = Task.Run(() => { Thread.Sleep(1500); Console.WriteLine("task启动"); }); Thread.Sleep(300); task.Wait(); Console.WriteLine("主线程结束");
开启新任务的方法:Task.Run()或者Task.Factory.StartNew(),开启的是后台线程。要在主线程中等待后台线程执行完毕,可使用Wait方法(会以同步的方式来执行)。不用Wait则会以异步的方式来执行。
比较一下Task和Thread:
static void Main(string[] args) { for (int i = 0; i < 5; i++) { new Thread(Run1).Start(); } for (int i = 0; i < 5; i++) { Task.Run(() => { Run2(); }); } } static void Run1() { Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId); /* Thread Id=10 Thread Id=11 Thread Id=12 Thread Id=13 Thread Id=14 */ } static void Run2() { Console.WriteLine("Task调用的Thread Id =" + Thread.CurrentThread.ManagedThreadId); /* Task调用的Thread Id =15 Task调用的Thread Id =6 Task调用的Thread Id =6 Task调用的Thread Id =17 Task调用的Thread Id =15 */ }
能够看出来,直接用Thread会开启5个线程,用Task(用了线程池)开启了3个!
Task<TResult>就是有返回值的Task,TResult就是返回值类型。
Console.WriteLine("主线程开始"); //返回值类型为string Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); //会等到task执行完毕才会输出; Console.WriteLine(task.Result); Console.WriteLine("主线程结束"); /* 主线程开始 10 主线程结束 */
经过task.Result能够取到返回值,若取值的时候,后台线程还没执行完,则会等待其执行完毕!Task任务能够经过CancellationTokenSource类来取消,感受用得很少
Task 提供了线程取消的操做,使用起来也是很是的简单。它本质上是靠异常来打断线程的执行,而且把Task的状态置为Cancel状态
要实现线程的取消须要一下步骤。
public void run_with_cancel(System.Threading.CancellationToken ct) { System.Console.WriteLine("ThreadWork1 run { "); ct.ThrowIfCancellationRequested(); for (int i = 0; i < 10; i++) { System.Console.WriteLine("ThreadWork1 : " + i); System.Threading.Thread.Sleep(200); ct.ThrowIfCancellationRequested(); } System.Console.WriteLine("ThreadWork1 run } "); } public void run_with_cancel(System.Threading.CancellationToken ct) { ct.ThrowIfCancellationRequested(); System.Console.WriteLine("ThreadWork2 run { "); for (int i = 0; i < 10; i++) { System.Console.WriteLine("ThreadWork2 : " + i * i); System.Threading.Thread.Sleep(300); ct.ThrowIfCancellationRequested(); } System.Console.WriteLine("ThreadWork2 run } "); }
static void StartT1(System.Threading.CancellationToken ct) { ThreadWork1 work1 = new ThreadWork1(); work1.run_with_cancel(ct); } static void StartT2(System.Threading.CancellationToken ct) { ThreadWork2 work2 = new ThreadWork2(); work2.run_with_cancel(ct); }
System.Threading.CancellationTokenSource cts =new System.Threading.CancellationTokenSource(); System.Threading.CancellationToken ct = cts.Token; Task t1 = new Task(() => StartT1(ct)); //传入Token Task t2 = new Task(() => StartT2(ct)); //传入Token t1.Start(); t2.Start(); System.Threading.Thread.Sleep(2000); cts.Cancel(); //触发取消 try{ Console.WriteLine("Main wait t1 t2 end {"); if (!Task.WaitAll(new Task[] { t1, t2 }, 5000)) { Console.WriteLine("Worker1 and Worker2 NOT complete within 5 seconds"); Console.WriteLine("Worker1 Status: " + t1.Status); Console.WriteLine("Worker2 Status: " + t2.Status); } else { Console.WriteLine("Worker1 and Worker2 complete within 5 seconds"); } } catch (AggregateException agg_ex) { foreach (Exception ex in agg_ex.InnerExceptions) { Console.WriteLine("Agg Exceptions: " + ex.ToString()); Console.WriteLine(""); } }
async/await是C#4.5中推出的,async关键字用来指定某个方法、Lambda表达式或匿名方法自动以异步的方式来调用,先上用法
static void Main(string[] args) { Console.WriteLine("-------主线程启动-------"); Task<int> task = GetStrLengthAsync(); Console.WriteLine("主线程继续执行"); Console.WriteLine("Task返回的值" + task.Result); Console.WriteLine("-------主线程结束-------"); } static async Task<int> GetStrLengthAsync() { Console.WriteLine("GetStrLengthAsync方法开始执行"); //此处返回的<string>中的字符串类型,而不是Task<string> string str = await GetString(); Console.WriteLine("GetStrLengthAsync方法执行结束"); return str.Length; } static Task<string> GetString() { Console.WriteLine("GetString方法开始执行") return Task<string>.Run(() => { Thread.Sleep(2000); return "GetString的返回值"; }); } /* -------主线程启动------- GetStrLengthAsync方法开始执行 GetString方法开始执行 主线程继续执行 GetStrLengthAsync方法执行结束 Task返回的值13 -------主线程结束------- */
async用来修饰方法,代表这个方法是异步的,声明的方法的返回类型必须为:void,Task或Task<TResult>。方法的执行结果或者任何异常都将直接反映在返回类型中
await必须用来修饰Task或Task<TResult>,并且只能出如今已经用async关键字修饰的异步方法中。一般状况下,async/await成对出现才有意义
能够看出来,main函数调用GetStrLengthAsync方法后,在await以前,都是同步执行的,直到遇到await关键字,main函数才返回继续执行
在遇到await关键字后,没有继续执行GetStrLengthAsync方法后面的操做,也没有立刻反回到main函数中,而是执行了GetString的第一行,以此能够判断await这里并无开启新的线程去执行GetString方法,而是以同步的方式让GetString方法执行,等到执行到GetString方法中的Task<string>.Run()的时候才由Task开启了后台线程!
被async标记的方法,意味着能够在方法内部使用await,这样该方法将会在一个await point(等待点)处被挂起,而且在等待的实例完成后该方法被异步唤醒。【注意:await point(等待点)处被挂起,并非说在代码中使用await SomeMethodAsync()处就挂起,而是在进入SomeMethodAsync()真正执行异步任务时被挂起,切记
不是被async标记的方法,就会被异步执行,刚开始都是同步开始执行。换句话说,方法被async标记不会影响方法是同步仍是异步的方式完成运行。事实上,async使得方法能被分解成几个部分,一部分同步运行,一些部分能够异步的运行(而这些部分正是使用await显示编码的部分),从而使得该方法能够异步的完成。调用async标记的方法,刚开始是同步执行的,只有当执行到await标记的方法中的异步任务时,才会挂起。
那么await的做用是什么呢?
await关键字告诉编译器在async标记的方法中插入一个可能的挂起/唤醒点。 逻辑上,这意味着当你写await someMethod();时,编译器将生成代码来检查someMethod()表明的操做是否已经完成。若是已经完成,则从await标记的唤醒点处继续开始同步执行;若是没有完成,将为等待的someMethod()生成一个continue委托,当someMethod()表明的操做完成的时候调用continue委托。这个continue委托将控制权从新返回到async方法对应的await唤醒点处。返回到await唤醒点处后,无论等待的someMethod()是否已经经完成,任何结果均可从Task中提取,或者若是someMethod()操做失败,发生的任何异常随Task一块儿返回或返回给SynchronizationContext。
能够从字面上理解,上面提到task.wait可让主线程等待后台线程执行完毕,await和wait相似,一样是等待,等待Task<string>.Run()开始的后台线程执行完毕,不一样的是await不会阻塞主线程,只会让GetStrLengthAsync方法暂停执行。
IAsyncResult自.NET1.1起就有了,包含可异步操做的方法的类须要实现它,Task类就实现了该接口
在不借助于Task的状况下怎么实现异步呢?
class Program { static void Main(string[] args) { Console.WriteLine("主程序开始--------------------"); int threadId; AsyncDemo ad = new AsyncDemo(); AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null); Thread.Sleep(0); Console.WriteLine("主线程线程 {0} 正在运行.",Thread.CurrentThread.ManagedThreadId) //会阻塞线程,直到后台线程执行完毕以后,才会往下执行 result.AsyncWaitHandle.WaitOne(); Console.WriteLine("主程序在作一些事情!!!"); //获取异步执行的结果 string returnValue = caller.EndInvoke(out threadId, result); //释放资源 result.AsyncWaitHandle.Close(); Console.WriteLine("主程序结束--------------------"); Console.Read(); } } public class AsyncDemo { //供后台线程执行的方法 public string TestMethod(int callDuration, out int threadId) { Console.WriteLine("测试方法开始执行."); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("测试方法执行的时间 {0}.", callDuration.ToString()); } } public delegate string AsyncMethodCaller(int callDuration, out int threadId); /* 主程序开始-------------------- 主线程线程9正在运行. 测试方法开始执行. 测试方法执行完毕. 主程序在作一些事情!!! 主程序结束-------------------- */
和Task的用法差别不是很大!result.AsyncWaitHandle.WaitOne()就相似Task的Wait。
数据并行是指使用Parallel.For()或Parallel.ForEach()方法以并行方式对数组或集合中的数据进行迭代。
Stopwatch watch1 = new Stopwatch(); watch1.Start(); for (int i = 1; i <= 10; i++) { Console.Write(i + ","); Thread.Sleep(1000); } watch1.Stop(); Console.WriteLine(watch1.Elapsed); Stopwatch watch2 = new Stopwatch(); watch2.Start(); //会调用线程池中的线程 Parallel.For(1, 11, i => { Console.WriteLine(i + ",线程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); }); watch2.Stop(); Console.WriteLine(watch2.Elapsed);
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 }; Parallel.ForEach<int>(list, n => { Console.WriteLine(n); Thread.Sleep(1000); });
Action[] actions = new Action[] { new Action(()=>{ Console.WriteLine("方法1"); }), new Action(()=>{ Console.WriteLine("方法2"); }) }; Parallel.Invoke(actions);
为并行运行而设计的LINQ查询为PLINQ。System.Linq命名空间的ParallelEnumerable中包含了一些扩展方法来支持PINQ查询。
int[] modThreeIsZero = (from num in source.AsParallel() where num % 3 == 0 orderby num descending select num).ToArray();
为了简洁(偷懒),文中全部Task<TResult>的返回值都是直接用task.result获取,这样若是后台任务没有执行完毕的话,主线程会等待其执行完毕。这样的话就和同步同样了,通常状况下不会这么用。简单演示一下Task回调函数的使用:
Console.WriteLine("主线程开始"); Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); //会等到任务执行完以后执行 task.GetAwaiter().OnCompleted(() => { Console.WriteLine(task.Result); }); Console.WriteLine("主线程结束"); Console.Read(); /* 主线程开始 主线程结束 10 */
OnCompleted中的代码会在任务执行完成以后执行!另外task.ContinueWith()也是一个重要的方法:
Console.WriteLine("主线程开始"); Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); task.GetAwaiter().OnCompleted(() => { Console.WriteLine(task.Result); }); task.ContinueWith(m=>{Console.WriteLine("第一个任务结束啦!我是第二个任务");}); Console.WriteLine("主线程结束"); Console.Read(); /* 主线程开始 主线程结束 10 第一个任务结束啦!我是第二个任务 */
ContinueWith()方法可让该后台线程继续执行新的任务,控制Task简单的任务执行顺序的方式。
用ContinueWith和Wait函数组合使用即可以控制任务的运行顺序。
class ThreadWork1 { public ThreadWork1() { } public List<string> run() { List<string> RetList = new List<string>(); System.Console.WriteLine("ThreadWork1 run { "); System.Console.WriteLine("ThreadWork1 running ... ... "); for (int i = 0; i < 100; i++) { RetList.Add("ThreadWork1 : " + i); //System.Console.WriteLine("ThreadWork1 : " + i); } System.Console.WriteLine("ThreadWork1 run } "); return RetList; } } class ThreadWork2 { public ThreadWork2() { } public List<string> run() { List<string> RetList = new List<string>(); System.Console.WriteLine("ThreadWork2 run { "); System.Console.WriteLine("ThreadWork2 running ... ... "); for (int i = 0; i < 100; i++) { RetList.Add("ThreadWork2 : " + i); //System.Console.WriteLine("ThreadWork2 : " + i * i); } System.Console.WriteLine("ThreadWork2 run } "); return RetList; } } class Program { static void StartT0() { System.Console.WriteLine("Hello I am T0 Task, sleep 3 seconds. when I am ready others GO!"); for (int i = 0; i < 3; i++) { Console.WriteLine("StartT0 sleeping ... ... " + i); System.Threading.Thread.Sleep(1000); } } static List<string> StartT1() { ThreadWork1 work1 = new ThreadWork1(); return work1.run(); } static List<string> StartT2() { ThreadWork2 work2 = new ThreadWork2(); return work2.run(); } static void Main(string[] args) { Console.WriteLine("Sample 3-4 Main {"); // The sequence of the task is: // T0 (Wait 3s) --> | // | --> T1 (Cacluate) | // | --> T2 (Cacluate) | // | --> T3 (Print) var t0 = Task.Factory.StartNew(() => StartT0()); var t1 = t0.ContinueWith((t) => StartT1()); var t2 = t0.ContinueWith((t) => StartT2()); Console.WriteLine("Main wait t1 t2 end {"); Task.WaitAll(t1, t2); Console.WriteLine("Main wait t1 t2 end }"); var t3 = Task.Factory.StartNew(() => { Console.WriteLine("============= T1 Result ============="); for (int i = 0; i < t1.Result.Count; i++) { Console.WriteLine(t1.Result[i]); } Console.WriteLine("============= ========= =============\n\n"); Console.WriteLine("============= T2 Result ============="); for (int i = 0; i < t2.Result.Count; i++) { Console.WriteLine(t2.Result[i]); } Console.WriteLine("============= ========= =============\n\n"); }, TaskCreationOptions.LongRunning); Console.WriteLine("Sample 3-4 Main }"); Console.ReadKey(); } }