淺析C#中的Thread ThreadPool Task和async/await


.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:根本就無法獲取正確的返回值,實則沒有等待異步執行,而是直接返回了。

 

以上,水平有限,如有不足,敬請指正。如有侵權 請聯系作者刪除。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM