一、前言
多線程操作一直是編程的常用操作,掌握好基本的操作可以讓程序運行的更加有效。本文不求大而全,只是將我自己工作中常常用到的多線程操作做個分類和總結。平時記性不好的時候還能看看。本文參考了多篇園子裡的精彩博文,在文章最後會貼出具體來源,感謝他們的無私奉獻。
二、關於線程
(1) 為何使用線程:
可以使用線程將代碼同其他代碼隔離,提高應用程序的可靠性;
可以使用線程來簡化編碼;
可以使用線程來實現並發執行。
(2) 進程、應用程序域以及線程的關系:
進程(Process)是Windows系統中的一個基本概念,它包含著一個運行程序所需要的資源。進程之間是相對獨立的,一個進程無法訪問另一個進程的數據(除非利用分布式計算方式),一個進程運行的失敗也不會影響其他進程的運行,Windows系統就是利用進程把工作劃分為多個獨立的區域的。進程可以理解為一個程序的基本邊界。
應用程序域(AppDomain)是一個程序運行的邏輯區域,它可以視為一個輕量級的進程,.NET的程序集正是在應用程序域中運行的,一個進程可以包含有多個應用程序域,一個應用程序域也可以包含多個程序集。在一個應用程序域中包含了一個或多個上下文context,使用上下文CLR就能夠把某些特殊對象的狀態放置在不同容器當中。
線程(Thread)是進程中的基本執行單元,在進程入口執行的第一個線程被視為這個進程的主線程。
關系圖如下:
三、Thread
Thread可能是除了Task之外用的最多的多線程類。一般用法:
// one thread Thread thread = new ThreadStart(functiion); thread.Start(); // thread.join Thread ThreadA = new Thread(delegate() { //do something }); Thread ThreadB = new Thread(delegate() { //do something; ThreadA.Join(); //do another thing }); //啟動線程 ThreadA.Start(); ThreadB.Start(); //一開始,兩個線程相互交替運行,當線程B運行到線程A的join時,會先讓線程A執行完,然後線程B再繼續執行。你可以理解為超車,一開始兩者互不相讓,當join時,線程A超車了線程B
四、ThreadPool
由於線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成內存資源的浪費,出於對性能的考慮,於是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然後委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在後台執行任務,又可以減少線程創建和銷毀所帶來的開銷。
如果一個線程的時間非常長,就沒必要用線程池了(不是不能作長時間操作,而是不宜。),況且我們還不能控制線程池中線程的開始、掛起、和中止。
ThreadPool.QueueUserWorkItem(function,parameter);
五、Task
Task是我用的最多的多線程方式,一般使用的方法如下:
// one task var task = new Task(() =>{
// do something
}); task.Start();
// task one by one
var task = new Task(() =>{
// do something
});
Task task2 = task.ContinueWith(()=>{
// do something
});
task.Start();
// many tasks var tasks = new Task[PackCount]; //多線程任務 for (int index = 0; index < PackCount; index++) { int Threadindex = index; var task = new Task(() => { // do something } }); tasks[Threadindex] = task; task.Start(); } Task.WaitAll(tasks); //等待所有線程完成 //Task.WaitAny(tasks); //等待一個線程完成繼續執行主程序
//Task.Factory
Task.Factory.StartNew(()=>{ // do something });
六、Invoke、BeginInvoke、DynamicInvoke
此處主要說明的是delegate下的各種Invoke
Invoke (委托方法執行在調用處同一個線程中)
delegate void MyDelegate(); MyDelegate del = new MyDelegate(Function); del .Invoke(); //使用到委托的invoke方法
BeginInvoke(它從線程池中抓取一個空閒線程,來委托執行方法)
A情況:使用IAsyncResult.IsCompleted判斷子線程是否執行完畢
delegate T MyDelegate(); MyDelegate del = new MyDelegate(Function); IAsyncResult result = del.BeginInvoke(parameter,null,null); //if the branch thread is not completed while(!result.IsCompleted) { // the main thread do another thing } T data = del.EndInvoke(result); // var data is the result of Function with parameter
B情況:使用IAsyncResult.AsyncWaitHandle.WaitOne(timeout)判斷子線程是否執行完畢
delegate T MyDelegate(); MyDelegate del = new MyDelegate(Function); IAsyncResult result = del.BeginInvoke(parameter,null,null); //if the branch thread is not completed while(!result.AsyncWaitHandle.WaitOne(int timeout)) { // the main thread do another thing } T data = del.EndInvoke(result); // var data is the result of Function with parameter
C情況:使用WaitHandle.WaitAll(WaitHandle[],timeout)判斷子線程是否執行完畢
delegate T MyDelegate(); MyDelegate del = new MyDelegate(Function); IAsyncResult result = del.BeginInvoke(parameter,null,null); WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle,........ }; while (!WaitHandle.WaitAll(waitHandleList,int timeout)) { // the main thread do another thing } T data = del.EndInvoke(result); // var data is the result of Function with parameter
D情況:使用輪詢方式來檢測異步方法的狀態非常麻煩,而且效率不高,為此需要使用回調函數。主線程可以安心做自己的事,而異步線程完成操作後執行回調函數即可。回調函數依然是在異步線程上,而非主線程上。
delegate T MyDelegate(); MyDelegate del = new MyDelegate(Function); IAsyncResult result = del.BeginInvoke(parameter,new AsyncCallback(callbackFunction),object); ....MainThread do somethng... static void callbackFunction(IAsyncResult result) { AsyncResult _result = (AsyncResult )result; MyDelegate del = (MyDelegate)_result.AsyncDelegate; T data = del.EndInvoke(_result); T1 objectReciever = (T1)result.AsyncResult; //object=result.AsyncResult }
DynamicInvoke:
與Delegate.Invoke類似,同步,且同線程,唯一不同的是,是采用後期綁定的方式來調用委托方法,所以時間代價較大。
工作實例:在WPF中出現一種異常:“調用線程無法訪問此對象,因為另一個線程擁有該對象。”
情況A:假設發生該異常的代碼是在xaml.cs文件中,那麼Dispatcher.Invoke已經夠用了。
情況B: 假設發生該異常的代碼是在.cs文件中,那麼在Stack Overflow上有一招:
1 private void RaiseEventOnUIThread(Delegate theEvent, object[] args) 2 { 3 foreach (Delegate d in theEvent.GetInvocationList()) 4 { 5 ISynchronizeInvoke syncer = d.Target as ISynchronizeInvoke; 6 if (syncer == null) //靜態函數為null
7 { 8 d.DynamicInvoke(args); 9 } 10 else 11 { 12 syncer.BeginInvoke(d, args); //在創建了此對象的線程上異步執行委托 13 } 14 } 15 }
References:
【1】http://blog.sina.com.cn/s/blog_5a6f39cf0100qtzf.html
【2】http://www.cnblogs.com/slikyn/articles/1525940.html
【3】http://kb.cnblogs.com/page/130487/#t3
【4】http://blog.csdn.net/soft_123456/article/details/38819877
【5】http://www.cnblogs.com/laoyur/archive/2011/04/14/2016025.html
【6】http://blog.csdn.net/cselmu9/article/details/8274556
【7】http://stackoverflow.com/questions/1698889/raise-events-in-net-on-the-main-ui-thread