很久以前的一個面試場景:
面試官:說說你對JavaScript閉包的理解吧?
我:嗯,平時都是前端工程師在寫JS,我們一般只管寫后端代碼。
面試官:你是后端程序員啊,好吧,那問問你多線程編程的問題吧。
我:一般沒用到多線程。
面試官:............................. (面試結束)
好了,哈哈一笑后,我們來看看 Thread,ThreadPool,Task, async, await 的基本使用方法。
1.Thread
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); var thread = new Thread(new ThreadStart(ThreadTest)); thread.Start(); System.Console.WriteLine("主線程結束"); System.Console.ReadLine(); } private static void ThreadTest() { System.Console.WriteLine("開始執行子線程.... "); Thread.Sleep(100); }
執行結果:
上面的代碼,大家應該都很好理解,通過new Thread 來創建一個子線程,然后.Start() 開始執行。
我們F12 ThreadStart 看到 public delegate void ThreadStart(); 是一個無參數無返回值的委托,那么,如果要在線程中執行一個有參數的方法怎么辦了?
OK,我們看Thread的構造函數
ParameterizedThreadStart 是什么?按字面上意思就是帶參數的ThreadStart,繼續F12看看它
果然是可以帶一個object的參數。
改造一下剛才的代碼:
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); var thread = new Thread(new ParameterizedThreadStart(ThreadTest)); thread.Start(10); System.Console.WriteLine("主線程結束"); System.Console.ReadLine(); } private static void ThreadTest(object p) { System.Console.WriteLine("開始執行子線程.... 參數:{0} ", p); Thread.Sleep(100); }
執行結果:
(當然還可以用ThreadStart(()=>{ }) 直接用lambda表達式的方式,這里就不寫示例代碼了 )
看到上面的執行結果,子線程因為Thread.Sleep(100) ,所以每次都最后才打印出輸出結果,那么你可能會疑問,如果我想等子線程執行完,我再執行主線程后面的代碼,怎么辦?
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); var thread = new Thread(new ParameterizedThreadStart(ThreadTest)); thread.Start(10); thread.Join(); System.Console.WriteLine("主線程結束"); System.Console.ReadLine(); }
private static void ThreadTest(object p) { System.Console.WriteLine("開始執行子線程.... 參數:{0} ", p); Thread.Sleep(100); }
注意看, 加了這句 thread.Join() ,管他什么意思,我們先看看執行結果吧!
OK,是不是明白Join()的意義了?
2.ThreadPool
為什么有了Thread還要出現ThreadPool了?
如果你的代碼設計了大量使用Thread,那么有可能會超過系統最大的線程數導致崩潰,而且每次創建和銷毀線程也是很耗資源,ThreadPool就可以幫你提高代碼效率並管理你的線程。
這不是重點,今天重點是學習它的基礎使用方法。
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadTest), 1); System.Console.WriteLine("主線程結束"); System.Console.ReadLine(); } private static void ThreadTest(object p) { System.Console.WriteLine("開始執行子線程.... 參數:{0} ", p); Thread.Sleep(100); }
先看看WaitCallback的定義
一個帶參數的委托,這就要求它的委托方法必須帶一個object的參數了。
ThreadPool靜態類通過QueueUserWorkItem()方法將工作函數排入線程池,它不需要我們主動的.Start(),那么他能不能Join()了?
我們點一下就知道了,它既不要你手動Start也沒有Join這樣的方法。
好了,簡單學習Thread和ThreadPool后,發現他們構造函數中都是沒有返回值的委托,如果我們要在主線程中獲取子線程執行方法的返回值,怎么辦? Task閃亮登場了!
3.Task
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); // new Task 創建方式-不帶參數 //Task task = new Task(ThreadTest); //task.Start(); // new Task 創建方式-帶參數 //Task task=new Task(() => ThreadTest(10)); //Task.Factory 創建方式-不帶參數 //Task task = Task.Factory.StartNew(ThreadTest); //task.Start(); //Task.Factory 創建方式-帶參數 //Task task = Task.Factory.StartNew(() => ThreadTest(10)); //task.Start(); Task task = Task.Run(() => ThreadTest()); //Task task = Task.Run(() => ThreadTest(10)); System.Console.WriteLine("主線程結束"); System.Console.ReadLine();
}
Task 的三種創建線程的方法,Task.Run() 不需要手動Start() 。其他兩種方式是需要手動Start()。 他們沒有Join()方法,取而代之的是Wait()
我們用Run()方法為例,看Task如何獲取方法的返回值。
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); Task<int> task = Task.Run(() => ThreadTest(10)); var result = task.Result; System.Console.WriteLine("主線程結束,result={0}", result); System.Console.ReadLine(); } private static int ThreadTest(int i) { Thread.Sleep(100); System.Console.WriteLine("子線程開始"); return i * 100; }
執行結果:
通過task.Result 我們獲取到了在子線程中ThreadTest方法的返回值。有沒有注意,主線程是等子線程執行完之后才打印最后輸出的! task.Result 除了拿到返回值外,是不是和Wait()類似?
看到這里,你肯定會想到,這樣另起線程去跑耗時作業和我們平時普通寫法有什么區別?效率上會高很多嗎?我們來測試看看!
常規方法:
private static void Main(string[] args) { DateTime dt1 = DateTime.Now; int count = 0; for (int i = 0; i < 10; i++) { Thread.Sleep(10); count += i; } System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds); System.Console.ReadLine(); }
Task方法:
private static void Main(string[] args) { DateTime dt1 = DateTime.Now; Task<int> task = Task.Run(() => { int count = 0; for (int i = 0; i < 10; i++) { Thread.Sleep(10); count += i; } return count; }); var result = task.Result; System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds); System.Console.ReadLine(); }
這就很尷尬了,用Task反而執行時間更長!!! 是不是我的打開方式不對?
4.async await
async是修飾一個異步方法的關鍵字。有兩種返回類型(void 或者 Task<T>)
await必須是在async修飾的異步方法體內,await后面必須是一個異步方法或者Task。表示異步等待后面方法的結果。
1.返回void的使用方法
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); for (int i = 0; i < 10; i++) { ThreadTest(i); } System.Console.WriteLine("主線程執行完成"); System.Console.ReadLine(); } private static async void ThreadTest(int i) { await Task.Run(() => { Thread.Sleep(10); System.Console.WriteLine("子線程開始,i=" + i); }); }
執行結果
2.返回Task<T>的使用方法
private static void Main(string[] args) { System.Console.WriteLine("主線程開始"); var task = ThreadTest(); System.Console.WriteLine("主線程執行完成,result="+ task.Result); System.Console.ReadLine(); } private static async Task<int> ThreadTest() { var count = 0; await Task.Run(() => { for (int i = 0; i < 10; i++) { Thread.Sleep(10); count += i; System.Console.WriteLine("count="+ count); } }); return count; }
返回的是Task<T>,那么像得到它的返回值,肯定也是通過.Result了,我們肯定有疑問了,這樣和直接寫Task有什么區別? 只是為了更加方便和美觀嗎?
接下來我們來測試下執行效率!
private static void Main(string[] args) { DateTime dt1 = DateTime.Now; var t = ThreadTest().Result; System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds + " count=" + t); System.Console.ReadLine(); } private static async Task<int> ThreadTest() { var count = 0; await Task.Run(() => { for (int i = 0; i < 10; i++) { Thread.Sleep(10); count += i; } }); return count; }
執行效率和之前沒什么區別,不知道這種測試方式是否合理?跪求大神們分享賜教!
今天就寫到這里,關於 async await 和Task區別,async await 線程阻塞問題,后面再來仔細研究。
========================================================================================================
分割線
========================================================================================================
昨天這篇博客發布后,收到大神的批評和指教,非常感謝!
讀了這篇文章后,才恍然大悟。 文章鏈接:https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx
分析下昨天測試“性能”的實例代碼的使用錯誤:
1.盲目使用task.Result來獲取最終結果,這樣導致主線程阻塞,都是等待子線程執行完畢。這樣的時間差比沒有太多意義。
2.都是在一個Task.Run()中模擬一個耗時操作,內部循環Thread.Sleep(10)。這樣把耗時操作搬到一個子線程去做,就算快也能快到哪里去了,完全沒有體現出多線程的優越性。
下面是修改后的測試代碼,再看看async await給程序帶來的性能:
1.普通Task,通過task.Result獲取返回值。
private static void Main(string[] args) { DateTime dt1 = DateTime.Now; for (int i = 0; i <= 50; i++) { var re = Task.Run(() => { Thread.Sleep(10); return i; }).Result; System.Console.WriteLine("result=" + re); if (i == 50) System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds); } System.Console.ReadLine(); }
2.使用async await
private static void Main(string[] args) { DateTime dt1 = DateTime.Now; for (int i = 0; i <= 50; i++) { var task = ThreadTest(dt1, i); } System.Console.ReadLine(); } private static async Task<int> ThreadTest(DateTime dt1, int i) { int re = await Task.Run(() => { Thread.Sleep(10); return i; }); System.Console.WriteLine("result=" + re); if (i == 50) System.Console.WriteLine("執行完成,耗時=" + (DateTime.Now - dt1).TotalMilliseconds); return re; }
async await 真正體現了它的性能所在 。
總結:
1.不要盲目使用task.Result
2.async await的意義(或者說和Task的區別)在於不阻塞線程的情況下獲取返回值。
本文博客園地址:http://www.cnblogs.com/struggle999/p/6933376.html