2019年2月6日 星期三

async 與 await重要觀念

https://www.huanlintalk.com/2016/01/async-and-await.html

https://www.youtube.com/watch?v=8vcrjhaF1zE

https://hiskio.com/courses/182/lectures/7553


範例:改成非同步呼叫

原本的 MyDownloadPage 是同步呼叫的寫法,底下是改成非同步呼叫的版本:

static async Task<string> MyDownloadPageAsync(string url)
{
    var webClient = new WebClient(); 
    Task<string> task = webClient.DownloadStringTaskAsync(url);
    string content = await task;
    return content;

}

光是方法簽名(signature)就有三處修改:

1.在宣告方法時,前面加上關鍵字 async,表示這是一個非同步方法,裡面會用到 await 關鍵字。

2.以 async 關鍵字宣告的方法若有回傳值,回傳的型別須以泛型 Task 表示。原先同步版本的方法是傳回 string,故此處改為 Task。非同步方法若不需要傳回值,則回傳型別應寫成 Task,而不要寫 void(原因後述)。


3.一般而言,非同步方法的名稱會以「Async」結尾,所以方法名稱現在改為MyDownloadPageAsync。

接著修改此方法的實作,把這行程式碼:


string content = webClient.DownloadString(url);

改成這樣:

string content = await webClient.DownloadStringAsync(url);

而這行程式碼也可以拆成兩行來寫:

Task<string> task = webClient.DownloadStringTaskAsync(url); // (1)
string content = await task; // (2)


說明:
原本呼叫 WebClient 類別的 DownloadString 方法,現在改呼叫它提供的非同步版本:DownloadStringTaskAsync。與其他非同步 I/O 方法類似,DownloadStringTaskAsync 方法的內部會起始一個非同步 I/O 工作,而且不等該工作完成便立即返回呼叫端;此時傳回的物件是個 Task,代表一個將傳回 string 的非同步 I/O 工作。
使用 await 關鍵字來等待非同步工作執行完畢,然後取得其執行結果。這裡的「等待」,是採取「非同步等待」的作法。意思是說,使用了關鍵字 await 的地方會暫且記住 await 敘述所在的程式碼位置,並且令程式控制流程立刻返回呼叫端;等到 await 所等待的那個非同步工作執行完畢,控制流才又會切回來繼續執行剛才保留而未執行的程式碼。

接下來要修改的是 Main 函式:

static void Main(string[] args)
{
    Task<string> task = MyDownloadPageAsync("http://huan-lin.blogspot.com");

    string content = task.Result; // 取得非同步工作的結果。

    Console.WriteLine("網頁內容總共為 {0} 個字元。", content.Length);
    Console.ReadKey();
}

這裡也是先取得非同步方法 MyDownloadPageAsync 所傳回的 Task 物件但這一次是用 Task Result 屬性來取得非同步工作的執行結果,而不是寫成 await task。其實這裡不能使用 await 關鍵字,因為有用到 await 的函式都必須在宣告時加上 async 關鍵字,否則無法通過編譯


前面提過,「await 某件工作」的寫法會令控制流立刻返回呼叫端。相較之下,「讀取 Task 物件的 Result 屬性」則是阻斷式(blocking)操作,也就是說,它會令當前的執行緒暫停,直到欲等待的工作執行完畢並傳回結果之後,才繼續往下執行。

沒有留言:

張貼留言