很久没安下心来写博客了,几年的开发过程中,对于异步与并行的了解也随着清淅起来。首先很多人问我,异步与并行的区别,那么我们来了解下概念。
本博文写的主旨是用最白话的语言来说明问题,不想照搬概念。
在古老的单核计算机中,一般是单核的,并行也只是在进程中交替的执行,表现出来的像并行执行一样,只是时间比较短,在多核处理器的计算机中,进程不仅可以交替执行,而且可以重叠执行,所以说,并行,只有在多核处理器中才有真正意义。很多人可能会突然不理解,并行与并发,是什么区别,并行,就像两种时刻相同的进程同一时刻运行,而并发不一定同一时刻运行,这就微妙的区别。
就拿订单来说吧,在下单超大的情况下,A用户下了一个订单,还没有来及结束,B用户又下了一个订单。那么,这时最有可能发行的情况就是并发事件。
那么我们今天重点说说异步,异步,是相对于同步来说的,我们知道,应用程序都是由一个主线程来运行的,这个主线程,是按照顺序来处理我们写的逻缉代码的,这就是同步,引用异步的好处是,在不打挠主线程的前提下,继续开放一个线程来指行其它的事情,就相当于,把部分工作交接给别人,当别人做好了后,然后,对我说,你交待的任务我已做好了。别人做好的工作交待给我的过程,相当于结果的返回中断。
异步最终的目的就是给我们带来更高效的时间效应,它是一种结果,而实现这个异步的可能是异步委托,线程池,线程等等,只不过是一种方法或途径罢了,这就是线程与异步的最好诠释。
下面来说说异步自然缺少不了多线程这个重头戏。
实现多线程方式很多,下面一个一个的讲起。
1.投票
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { public delegate decimal TakeDelegate(decimal data, int ms); static void Main(string[] args) { DateTime now = DateTime.Now; TakeDelegate dl = SaveBankAccount; IAsyncResult ar= dl.BeginInvoke(1, 200, null, null); while(!ar.IsCompleted) { Console.WriteLine("main thread wating current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(50); } decimal result = dl.EndInvoke(ar); Console.WriteLine("CurrentMoney:{0}", result); Console.WriteLine("runtime:{0}", (DateTime.Now-now).TotalSeconds); Console.WriteLine("main thread IsBackground " + Thread.CurrentThread.IsBackground); Console.ReadKey(); } static decimal SaveBankAccount(decimal money,int ms) { Console.WriteLine("SaveBankAccount thread started! current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("SaveBankAccount thread IsBackground " + Thread.CurrentThread.IsBackground); Thread.Sleep(ms); Console.WriteLine("SaveBankAccount thread completed!"); return ++money; } } }
一直都在想一个问题,为什么这种方式名字叫投票!百度 google都没有结果,最后想着想着也就想清楚了,所谓投票,就是对结果的一种猜测,“是否结束投票”
2.异步回调
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { public delegate decimal TakeDelegate(decimal money,int ms); static DateTime now = DateTime.Now; static void Main(string[] args) { TakeDelegate dl = SaveBankAccount; var ar = dl.BeginInvoke(1, 200, AsyncCallBack,dl); while(!ar.IsCompleted) { Console.WriteLine("main thread wating current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(50); } Console.ReadKey(); } static decimal SaveBankAccount(decimal money,int ms) { Console.WriteLine("SaveBankAccount thread started! current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("SaveBankAccount thread IsBackground " + Thread.CurrentThread.IsBackground); Thread.Sleep(ms); Console.WriteLine("SaveBankAccount thread completed!"); return ++money; } static void AsyncCallBack(IAsyncResult ar) { if (ar == null) { throw new ArgumentNullException("ar"); } TakeDelegate dl = ar.AsyncState as TakeDelegate; decimal result = dl.EndInvoke(ar); Console.WriteLine("CurrentMoney:{0}", result); Console.WriteLine("runtime:{0}", (DateTime.Now - now).TotalSeconds); Console.WriteLine("main thread IsBackground " + Thread.CurrentThread.IsBackground); } } }
这个方法是在投票的基础,加入了回调函数而已。还有一种方法于投票差不多,就是等待句柄(AsyncWaitHandle),这个方法与投票没有太大的差异。没事的同学可以百度一下,这里就不多说了。
如果说到这里,那么,我想微软也太失败了,因为这样玩异步太操心,那么多的代码。
随着时间的推移,微软在.net3.0 C# 3.0的大包裹越来越健全 拉姆达(lambda)表达式与匿名方法 孕育而生。
好吧,我们对上面的代码是时候要简化的必要了。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { DateTime now = DateTime.Now; Func<decimal, int, decimal> f = SaveBankAccount; var ar = f.BeginInvoke(1, 200, (r) => { if (r == null) { throw new ArgumentNullException("r"); } Console.WriteLine("CurrentMoney:{0}", f.EndInvoke(r)); Console.WriteLine("runtime:{0}", (DateTime.Now - now).TotalSeconds); Console.WriteLine("main thread IsBackground " + Thread.CurrentThread.IsBackground); }, null); while (!ar.IsCompleted) { Console.WriteLine("main thread wating current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(50); } Console.ReadKey(); } static decimal SaveBankAccount(decimal money, int ms) { Console.WriteLine("SaveBankAccount thread started! current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("SaveBankAccount thread IsBackground " + Thread.CurrentThread.IsBackground); Thread.Sleep(ms); Console.WriteLine("SaveBankAccount thread completed!"); return ++money; } } }
再次狠狠的优化
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { DateTime now = DateTime.Now; Func<decimal, int, decimal> f = (money, ms) => { Console.WriteLine("SaveBankAccount thread started! current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("SaveBankAccount thread IsBackground " + Thread.CurrentThread.IsBackground); Thread.Sleep(ms); Console.WriteLine("SaveBankAccount thread completed!"); return ++money; }; var ar = f.BeginInvoke(1, 200, (r) => { if (r == null) { throw new ArgumentNullException("r"); } Console.WriteLine("CurrentMoney:{0}", f.EndInvoke(r)); Console.WriteLine("runtime:{0}", (DateTime.Now - now).TotalSeconds); Console.WriteLine("main thread IsBackground " + Thread.CurrentThread.IsBackground); }, null); while (!ar.IsCompleted) { Console.WriteLine("main thread wating current run at treadID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(50); } Console.ReadKey(); } } }
着重看下两个划框的部分,lambda表达式采用匿名方法成功的简化了传统的方式,里面的参数获取,将来的更为便捷,所以四个参数后,用了null。
public delegate TResult Func<in T, out TResult>(T arg) 委托 是微软在.net 3.5 再次对delegate的封装,第一个参数是输入参数,第二个是返回参数,此时,我们不用太辛苦的到处声明委托,这点微软也给我们省了,不得不说,代码的优美不是java能比的。
细心的朋友可能看到上面一段代码 IsBackground 这个时候,表示线程是前台线程还是后台线程。
前台线程与后台线程的区别:
就像word文档一样,打开word 即开启了word主线程即前台线程,诸如 语法检查属于后台线程,仔细想想,这样设计,还是有道理的,当关闭了word前台主线程,后台线程语法检查也没有必要了。
一个进程里必须有一个前台线程,不一定有后台线程。当前台线程已结束的时候,后台线程也将结束。
看下面代码
通常,应该将被动侦听活动的线程设置为后台线程,而将负责发送数据的线程设置为前台线程,这样,在所有的数据发送完毕之前该线程不会被终止。只有在确认线程被系统随意终止没有不利影响时,才应该使用后台线程。如果线程正在执行必须完成的敏感操作或事务操作,或者需要控制关闭线程的方式以便释放重要资源,则使用前台线程。
例如 《C#高级编程》中有个例子--如果关闭Word程序,拼写检查器还在运行其进程就没有意义了。在关闭应用程序时拼写检查器线程就可以关闭。
小结:本节只是对于基础知识线程与异步委托作了个简单的复习,让我联想到,主线程也好,新开的线程也好,无非都是线程的部分,线程更多的是一种方法,而异步是一个需要线程支撑的结果,所以可以在任何线程上开启异步的操作,因为主线程都可以开启异步嘛。
未完待续...