很久沒安下心來寫博客了,幾年的開發過程中,對於異步與並行的了解也隨着清淅起來。首先很多人問我,異步與並行的區別,那么我們來了解下概念。
本博文寫的主旨是用最白話的語言來說明問題,不想照搬概念。
在古老的單核計算機中,一般是單核的,並行也只是在進程中交替的執行,表現出來的像並行執行一樣,只是時間比較短,在多核處理器的計算機中,進程不僅可以交替執行,而且可以重疊執行,所以說,並行,只有在多核處理器中才有真正意義。很多人可能會突然不理解,並行與並發,是什么區別,並行,就像兩種時刻相同的進程同一時刻運行,而並發不一定同一時刻運行,這就微妙的區別。
就拿訂單來說吧,在下單超大的情況下,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程序,拼寫檢查器還在運行其進程就沒有意義了。在關閉應用程序時拼寫檢查器線程就可以關閉。
小結:本節只是對於基礎知識線程與異步委托作了個簡單的復習,讓我聯想到,主線程也好,新開的線程也好,無非都是線程的部分,線程更多的是一種方法,而異步是一個需要線程支撐的結果,所以可以在任何線程上開啟異步的操作,因為主線程都可以開啟異步嘛。
未完待續...