.net 項目中不可避免地要與線程打交道,目的都是實現異步、並發。從最開始的new Thread()入門,到后來的Task.Run(),如今在使用async/await的時候卻有很多疑問。
先來看一段代碼:使用Task實現異步
Task.Run(() => { message = (IBytesMessage)consumer.Receive(m_Interval); });
Receive()方法是一個延遲返回的方法,m_Interval是超時時間。如果采用同步方式執行Receive()的話,那整個程序就會被這個方法堵塞。我個人最習慣的處理方式就用Task.Run()。可惜項目要求必須使用.net framework3.5,所以只能退而求其次,放棄Task,使用Thread或者ThreadPool。
使用Thread實現異步:
new Thread(() => { message = (IBytesMessage)consumer.Receive(m_Interval); }).Start();
直接new Thread().start()這個寫法是很危險的,這里只做參考。在C# 5以后的書籍中,你可能會看到這樣一句話:一旦你輸入了new Thread(),那就糟糕了,說明項目的代碼太過時了。
使用ThreadPool實現異步:
ThreadPool.QueueUserWorkItem(Listen);
private void Listen(object state) { message = (IBytesMessage)consumer.Receive(m_Interval); }
ThreadPool 內部有一套完整的線程管理機制,可以讓開發者完全忽略Thread的生命周期控制。但ThreadPool中的線程,都是后台線程,當主線程執行完畢時,程序並不會等待后台線程的執行,而是直接退出。Thread則是前台線程,主程序會等待所有前台線程執行完畢后才會退出。另外在使用ThreadPool的時候需要注意QueueUserWorkItem的參數類型是:
public delegate void WaitCallback(object state) 所以,Listen方法有一個未用到的參數state。
綜上,Task還是最優的解決方案。
說到這,問題看似解決了,.net 4.0及以上 Task是不二之選,低版本擇優先選擇ThreadPool,特殊情況考慮Thread。那么 .net4.5的新特性 async/await 有什么用呢?上述情況需要用到async/await 嗎?
這里我們需要看一下完整的代碼
private void Listen(object state) { message = (IBytesMessage)consumer.Receive(m_Interval); if (message != null) { m_IAsyncMesssgae.OutputMessage(message.ToString()); } else { m_IAsyncMesssgae.OutputException(new Exception("Wait timed out.")); } } public void OnStartAsync() { try { if (m_IsConnected && !m_IsListening) { connectionWPM?.Start(); m_IsListening = true; ThreadPool.QueueUserWorkItem(Listen); } } catch (Exception ex) { m_IAsyncMesssgae.OutputException(ex); } finally { OnStop(); } }
這里紅色字體的m_IAsyncMesssgae是一個回調的接口實例,也就說,此代碼中,通過接口回調的方式把Receive()方法延遲返回的message返回給調用者。目前的代碼是可以滿足需求的。
我們試着用asynv和await實現一下這個需求。
public async void OnStartAsync() { if (m_IsConnected && !m_IsListening) { connectionWPM?.Start(); m_IsListening = true; message = await Task.Run(()=> {return (IBytesMessage)consumer.Receive(m_Interval); }); } }
1)async/await 和剛才說的Thread Task ThreadPool並不是一個概念。前者是控制異步和並發的關鍵字,后者是對線程的三種實現方式。
2)async/await只能和Task結合使用,async標記的方法 只能有三種返回值Task,Task<T>,void(不建議,因為async/await 就是為了獲取異步方法的返回值)。
3)await等待的內容也必須是Task或者Task<T> 上面代碼隱藏了一個內容,其實Task.Run()也是一個返回值為Task<T>的方法。
4)await還有一個作用是將Task<T>轉成T。
5)在同一個用async標記的方法內,所有在await代碼段之后的代碼 都要等待await后的內容執行完成后才能執行。
6)如果一個非async方法 調用async方法獲取異步返回值,那么就無法成功獲取異步返回值。
再把返回值void修改一下:
public async Task<IBytesMessage> OnStartAsync() { if (m_IsConnected && !m_IsListening) { connectionWPM?.Start(); m_IsListening = true; message = await Task.Run(()=> {return (IBytesMessage)consumer.Receive(m_Interval); }); } return message; }
這樣一來,外部調用時候,就不需要接口回調了,直接調用OnStartAsync就可以了。切記!調用OnStartAsync的方法必須也是async,否則就直接返回message的默認值,而不是等待TaskRun()的執行。await只在所屬的async方法內奏效。
調用OnStartAsync也有種不同的寫法:
//寫法1 async Task Handle() { string re = await OnStartAsync(); //dosth } //寫法2 async Task Handle() { var re = OnStartAsync(); //dosth do(await re); }
//寫法3
void Handle() { var re = OnStartAsync(); do(re); }
寫法1:dosth需要等待 OnStartAsync執行完畢后再執行。
寫法2:dosth先執行,然后再執行do(await re)
寫法3:根本就無法獲取正確的返回值,實則沒有等待異步執行,而是直接返回了。
以上,水平有限,如有不足,敬請指正。如有侵權 請聯系作者刪除。