您可以使用 async/await 功能,以更容易且直觀的方式撰寫非同步程式。 您可以撰寫非同步程式碼,使其看起來像是同步程式碼,讓編譯器處理困難的回呼函式和非同步程式碼通常需要的接續。
如需非同步功能的詳細資訊,請參閱使用 Async 和 Await 進行非同步程式設計 (Visual Basic)。
本逐步解說從同步化 Windows Presentation Foundation (WPF) 應用程式開始,該應用程式會加總網站清單中的位元組數目。 然後逐步解說會藉由使用新功能,將應用程式轉換為非同步解決方案。
您可以藉由完成此逐步解說,或從 .NET 範例瀏覽器下載程式碼來開發應用程式。 範例程式碼位於 SerialAsyncExample 專案中。
在這個逐步解說中,您將完成下列工作:
如需完整的非同步範例資訊,請參閱範例一節。
必要條件
您的電腦上必須安裝 Visual Studio 2012 或更新版本。 如需詳細資訊,請參閱 Visual Studio 下載頁面。
建立 WPF 應用程式
啟動 Visual Studio。
在功能表列上,選擇 [ 檔案]、[ 新增]、[ 專案]。
[ 新增專案 ] 對話方塊隨即開啟。
在 [已安裝的範本] 窗格中,選擇 [Visual Basic],然後從專案類型清單中選擇 [WPF 應用程式]。
在 [名稱] 文字方塊中,輸入
AsyncExampleWPF,然後選擇 [確定] 按鈕。新的專案隨即會出現在方案總管中。
設計簡單的 WPF MainWindow
在 Visual Studio 程式碼編輯器中,選擇 [ MainWindow.xaml ] 索引標籤。
如果未顯示 [工具箱] 視窗,請選擇 [檢視] 功能表,然後選擇 [工具箱]。
將 Button 控制項和 TextBox 控制項加入 [MainWindow] 視窗。
反白顯示 TextBox 控制項,並在 [屬性] 視窗中,設定下列值:
將 [名稱] 屬性設定為
resultsTextBox。將 [高度] 屬性設為 250。
將 [寬度] 屬性設為 500。
在 [文字] 索引標籤上,指定等寬字型,例如 Lucida Console 或全域等寬。
反白顯示 Button 控制項,並在 [屬性] 視窗中,設定下列值:
將 [名稱] 屬性設定為
startButton。將 [內容] 屬性的值從 Button 變更為 Start。
放置文字方塊和按鈕,使兩者都出現在 [MainWindow] 視窗中。
如需 WPF XAML 設計工具的詳細資訊,請參閱使用 XAML 設計工具建立 UI。
加入參考
在方案總管中,反白顯示您的專案名稱。
在功能表列上,選擇 [專案]、[加入參考]。
[參考管理員] 對話方塊隨即顯示。
在對話方塊上方,請確認專案的目標是 .NET Framework 4.5 或更新版本。
在 [組件] 區域中,選擇 [Framework] (如果尚未選擇)。
在名稱清單中,選取 [System.Net.Http] 核取方塊。
選擇 [確定] 按鈕以關閉對話方塊。
新增必要的匯入陳述式
在 [方案總管] 中,開啟 MainWindow.xaml.vb 的捷徑功能表,然後選擇 [檢視程式碼]。
如果尚未顯示,請將下列
Imports陳述式加入程式碼檔案頂端。Imports System.Net.Http Imports System.Net Imports System.IO
建立同步應用程式
在設計視窗 MainWindow.xaml 中,按兩下 [開始] 按鈕,以在 MainWindow.xaml.vb 中建立
startButton_Click事件處理常式。在 MainWindow.xaml.vb 中,將下列程式碼複製到
startButton_Click的內文:resultsTextBox.Clear() SumPageSizes() resultsTextBox.Text &= vbCrLf & "Control returned to startButton_Click."程式碼會呼叫方法,該方法會驅動應用程式
SumPageSizes,並且在控制項返回startButton_Click時顯示訊息。同步方案的程式碼包含下列四種方法:
SumPageSizes,會從SetUpURLList取得網頁 URL 的清單,然後呼叫GetURLContents和DisplayResults以處理每個 URL。SetUpURLList,會製作並傳回網址清單。GetURLContents,會下載每個網站的內容,並傳回內容做為位元組陣列。DisplayResults,會顯示每個 URL 的位元組陣列中的位元組數目。
複製下列四種方法,然後貼在 MainWindow.xaml.vb 的
startButton_Click事件處理常式下方:Private Sub SumPageSizes() ' Make a list of web addresses. Dim urlList As List(Of String) = SetUpURLList() Dim total = 0 For Each url In urlList ' GetURLContents returns the contents of url as a byte array. Dim urlContents As Byte() = GetURLContents(url) DisplayResults(url, urlContents) ' Update the total. total += urlContents.Length Next ' Display the total count for all of the web addresses. resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf & "Total bytes returned: {0}" & vbCrLf, total) End Sub Private Function SetUpURLList() As List(Of String) Dim urls = New List(Of String) From { "https://msdn.microsoft.com/library/windows/apps/br211380.aspx", "https://msdn.microsoft.com", "https://msdn.microsoft.com/library/hh290136.aspx", "https://msdn.microsoft.com/library/ee256749.aspx", "https://msdn.microsoft.com/library/hh290138.aspx", "https://msdn.microsoft.com/library/hh290140.aspx", "https://msdn.microsoft.com/library/dd470362.aspx", "https://msdn.microsoft.com/library/aa578028.aspx", "https://msdn.microsoft.com/library/ms404677.aspx", "https://msdn.microsoft.com/library/ff730837.aspx" } Return urls End Function Private Function GetURLContents(url As String) As Byte() ' The downloaded resource ends up in the variable named content. Dim content = New MemoryStream() ' Initialize an HttpWebRequest for the current URL. Dim webReq = CType(WebRequest.Create(url), HttpWebRequest) ' Send the request to the Internet resource and wait for ' the response. ' Note: you can't use HttpWebRequest.GetResponse in a Windows Store app. Using response As WebResponse = webReq.GetResponse() ' Get the data stream that is associated with the specified URL. Using responseStream As Stream = response.GetResponseStream() ' Read the bytes in responseStream and copy them to content. responseStream.CopyTo(content) End Using End Using ' Return the result as a byte array. Return content.ToArray() End Function Private Sub DisplayResults(url As String, content As Byte()) ' Display the length of each website. The string format ' is designed to be used with a monospaced font, such as ' Lucida Console or Global Monospace. Dim bytes = content.Length ' Strip off the "https://". Dim displayURL = url.Replace("https://", "") resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes) End Sub
測試同步解決方案
選擇 F5 鍵以執行程式,然後選擇 [ 開始 ] 按鈕。
應該會顯示如下列清單的輸出:
msdn.microsoft.com/library/windows/apps/br211380.aspx 383832 msdn.microsoft.com 33964 msdn.microsoft.com/library/hh290136.aspx 225793 msdn.microsoft.com/library/ee256749.aspx 143577 msdn.microsoft.com/library/hh290138.aspx 237372 msdn.microsoft.com/library/hh290140.aspx 128279 msdn.microsoft.com/library/dd470362.aspx 157649 msdn.microsoft.com/library/aa578028.aspx 204457 msdn.microsoft.com/library/ms404677.aspx 176405 msdn.microsoft.com/library/ff730837.aspx 143474 Total bytes returned: 1834802 Control returned to startButton_Click.請注意,需要花費幾秒鐘以顯示計數。 在這段期間,在等候下載要求資源的同時,會封鎖 UI 執行緒。 如此一來,您就無法在選擇 [開始] 按鈕之後,移動、最大化、最小化,或甚至關閉顯示視窗。 這些努力會失敗,直到位元組計數開始出現為止。 如果網站沒有回應,也不會指出失敗的站台。 甚至難以停止等候以及關閉程式。
將 GetURLContents 轉換為非同步方法
若要將同步方案轉換成非同步方案,最佳的起點是在
GetURLContents,因為呼叫 HttpWebRequest.GetResponse 方法及 Stream.CopyTo 方法是應用程式存取 Web 的位置。 .NET Framework 會讓轉換變得簡單,方法是提供這兩種方法的非同步版本。如需
GetURLContents中所用之方法的詳細資訊,請參閱 WebRequest。注意
當您依照本逐步解說中的步驟時,會出現數個編譯器錯誤。 您可以略過它們,並繼續進行本逐步解說。
將在
GetURLContents的第三行呼叫的方法從GetResponse變更為非同步、以工作為基礎的 GetResponseAsync 方法。Using response As WebResponse = webReq.GetResponseAsync()GetResponseAsync會傳回 Task<TResult>。 在此情況下,工作傳回變數TResult具有類型 WebResponse。 工作承諾會在已下載要求的資料及工作執行完成之後,產生實際WebResponse物件。若要從工作擷取
WebResponse值,請將 await 運算子套用至GetResponseAsync的呼叫,如下列程式碼所示。Using response As WebResponse = Await webReq.GetResponseAsync()Await運算子會暫停執行目前的GetURLContents方法,直到等候的工作完成為止。 同時,控制項會返回非同步方法的呼叫端。 在此範例中,目前方法是GetURLContents,呼叫端是SumPageSizes。 當工作完成時,承諾的WebResponse物件會以等候工作之值的形式產生,而且會指派給變數response。前一個陳述式可分成下列兩個陳述式,以釐清會發生什麼情況。
Dim responseTask As Task(Of WebResponse) = webReq.GetResponseAsync() Using response As WebResponse = Await responseTask對
webReq.GetResponseAsync的呼叫會傳回Task(Of WebResponse)或Task<WebResponse>。Await運算子會套用至工作以擷取WebResponse值。如果非同步方法有不需要依賴工作完成的工作要執行,此方法可以在呼叫非同步方法之後,以及套用 await 運算子之前,繼續這兩個陳述式之間的工作。 如需範例,請參閱如何:使用 Async 和 Await,同時發出多個 Web 要求 (Visual Basic) 以及如何:使用 Task.WhenAll 擴充非同步逐步解說的內容 (Visual Basic)。
由於您在上一個步驟中加入
Await運算子,所以發生編譯器錯誤。 此運算子只能用於以 async 修飾詞標示的方法。 當您重複轉換步驟以將對CopyTo的呼叫取代為對CopyToAsync的呼叫時,略過錯誤。變更方法的名稱,該方法會呼叫 CopyToAsync。
CopyTo或CopyToAsync方法會將位元組複製到其引數,content,並不會傳回有意義的值。 在同步版本中,呼叫CopyTo是簡單的陳述式,不會傳回值。 非同步版本,CopyToAsync,傳回 Task。 工作函式,例如 "Task(void)",讓方法等候。 將Await或await套用至對CopyToAsync的呼叫,如下列程式碼所示。Await responseStream.CopyToAsync(content)前一個陳述式縮寫下列兩行程式碼。
' CopyToAsync returns a Task, not a Task<T>. Dim copyTask As Task = responseStream.CopyToAsync(content) ' When copyTask is completed, content contains a copy of ' responseStream. Await copyTask
GetURLContents中還需要完成的工作是調整方法簽章。 您只能在以Await修飾詞標示的方法中使用 運算子。 新增修飾詞以將方法標示為「非同步方法」,如下列程式碼所示。Private Async Function GetURLContents(url As String) As Byte()非同步方法的傳回型別只能是 Task、Task<TResult>。 在 Visual Basic 中,方法必須是
Function,會傳回Task或Task(Of T),或者方法必須是Sub。 一般而言,Sub方法只適用於非同步事件處理常式,其中Sub為必要項目。 在其他情況下,如果完成的方法有Task(T)陳述式,則會傳回類型 T 的值,請使用 ;如果完成的方法不會傳回有意義的值,則使用Task。如需詳細資訊,請參閱非同步傳回型別 (Visual Basic)。
方法
GetURLContents有 return 陳述式,陳述式會傳回位元組陣列。 因此,非同步版本的傳回類型是 Task(T),其中 T 是位元組陣列。 在方法簽章中進行下列變更:將傳回型別變更為
Task(Of Byte())。依照慣例,非同步方法的名稱會以 "Async" 結尾,所以重新命名方法
GetURLContentsAsync。
下列程式碼會顯示這些變更。
Private Async Function GetURLContentsAsync(url As String) As Task(Of Byte())只要這幾個變更,
GetURLContents至非同步方法的轉換就能完成。
將 SumPageSizes 轉換為非同步方法
針對
SumPageSizes重複上述程序的步驟。 首先,將對GetURLContents的呼叫變更為非同步呼叫。如果您尚未這麼做,請變更方法的名稱,該方法是從
GetURLContents呼叫至GetURLContentsAsync。將
Await套用至工作,GetURLContentsAsync會傳回該工作以取得位元組陣列值。
下列程式碼會顯示這些變更。
Dim urlContents As Byte() = Await GetURLContentsAsync(url)前一個指派縮寫下列兩行程式碼。
' GetURLContentsAsync returns a task. At completion, the task ' produces a byte array. Dim getContentsTask As Task(Of Byte()) = GetURLContentsAsync(url) Dim urlContents As Byte() = Await getContentsTask在方法簽章中進行下列變更:
以
Async修飾詞來標示方法。將 "Async" 加入至方法名稱。
這次沒有任何工作傳回變數,T,因為
SumPageSizesAsync並未傳回 T 的值。(這個方法沒有任何Return陳述式)。不過,方法必須傳回Task才可以等候。 因此將方法類型從Sub變更為Function。 函式的傳回類型是Task。
下列程式碼會顯示這些變更。
Private Async Function SumPageSizesAsync() As TaskSumPageSizes至SumPageSizesAsync的轉換完成。
將 startButton_Click 轉換為非同步方法
如果您尚未這麼做,請在事件處理常式中,將呼叫方法的名稱從
SumPageSizes變更為SumPageSizesAsync。因為
SumPageSizesAsync是非同步方法,在事件處理常式中變更程式碼以等候結果。對
SumPageSizesAsync的呼叫會鏡射CopyToAsync中對GetURLContentsAsync的呼叫。 呼叫會傳回Task,而不是Task(T)。如同先前的程序,您可以使用一個陳述式或兩個陳述式轉換呼叫。 下列程式碼會顯示這些變更。
' One-step async call. Await SumPageSizesAsync() ' Two-step async call. Dim sumTask As Task = SumPageSizesAsync() Await sumTask若要避免不小心重新進入作業,請在
startButton_Click頂端加入下列陳述式以停用 [開始] 按鈕。' Disable the button until the operation is complete. startButton.IsEnabled = False您可以在事件處理常式結尾重新啟用按鈕。
' Reenable the button in case you want to run the operation again. startButton.IsEnabled = True如需重新進入的詳細資訊,請參閱處理非同步應用程式中的重新進入 (Visual Basic)。
最後,將
Async修飾詞加入宣告,讓事件處理常式可以等候SumPagSizesAsync。Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click一般而言,事件處理常式的名稱並沒有改變。 傳回的類型不會變更為
Task,因為事件處理常式必須在 Visual Basic 中為Sub程序。專案從同步到非同步處理的轉換已完成。
測試非同步解決方案
選擇 F5 鍵以執行程式,然後選擇 [ 開始 ] 按鈕。
類似同步方案的輸出應該會顯示。 但是,請注意下列差異。
處理完成之後,結果不會同時發生。 例如,這兩個程式在
startButton_Click中都包含程式碼行,會清除文字方塊。 此用意是在顯示一個結果集之後、二度選擇 [開始] 按鈕時,清除執行之間的文字方塊。 在同步版本中,當下載完成且 UI 執行緒可以執行其他工作時,會在第二次顯示計數之前,清除文字方塊。 在非同步版本中,會在您選擇 [開始] 按鈕之後,立即清除文字方塊。最重要的是,不會在下載期間封鎖 UI 執行緒。 您可以移動視窗或調整其大小,同時下載、計算及顯示 Web 資源。 如果其中一個網站變慢或沒有回應,您可以選擇 [關閉] 按鈕 (右上角紅色欄位中的 x),取消作業。
將 GetURLContentsAsync 方法取代為 .NET Framework 方法
.NET Framework 提供許多您可以使用的非同步方法。 其中一個方法 (HttpClient.GetByteArrayAsync(String) 方法) 會執行這個逐步解說所需的工作。 您可以使用這個方法,而不是
GetURLContentsAsync方法,這是您在先前的程序中建立的方法。第一個步驟是在方法 HttpClient 中建立
SumPageSizesAsync物件。 在方法的開頭,加入下列宣告。' Declare an HttpClient object and increase the buffer size. The ' default buffer size is 65,536. Dim client As HttpClient = New HttpClient() With {.MaxResponseContentBufferSize = 1000000}在
SumPageSizesAsync中,將方法的呼叫GetURLContentsAsync取代為 方法的HttpClient呼叫。Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)移除或取消註解您撰寫的
GetURLContentsAsync方法。選擇 F5 鍵以執行程式,然後選擇 [ 開始 ] 按鈕。
此版本之專案的行為應該符合「測試非同步方案」程序描述的行為,而且您只需要投入較少的精力。
範例
以下是使用非同步方法已轉換非同步 GetURLContentsAsync 解決方案的完整範例。 請注意,它極為類似原始的同步方案。
' Add the following Imports statements, and add a reference for System.Net.Http.
Imports System.Net.Http
Imports System.Net
Imports System.IO
Class MainWindow
Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click
' Disable the button until the operation is complete.
startButton.IsEnabled = False
resultsTextBox.Clear()
'' One-step async call.
Await SumPageSizesAsync()
' Two-step async call.
'Dim sumTask As Task = SumPageSizesAsync()
'Await sumTask
resultsTextBox.Text &= vbCrLf & "Control returned to button1_Click."
' Reenable the button in case you want to run the operation again.
startButton.IsEnabled = True
End Sub
Private Async Function SumPageSizesAsync() As Task
' Make a list of web addresses.
Dim urlList As List(Of String) = SetUpURLList()
Dim total = 0
For Each url In urlList
Dim urlContents As Byte() = Await GetURLContentsAsync(url)
' The previous line abbreviates the following two assignment statements.
'//<snippet21>
' GetURLContentsAsync returns a task. At completion, the task
' produces a byte array.
'Dim getContentsTask As Task(Of Byte()) = GetURLContentsAsync(url)
'Dim urlContents As Byte() = Await getContentsTask
DisplayResults(url, urlContents)
' Update the total.
total += urlContents.Length
Next
' Display the total count for all of the websites.
resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf &
"Total bytes returned: {0}" & vbCrLf, total)
End Function
Private Function SetUpURLList() As List(Of String)
Dim urls = New List(Of String) From
{
"https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"https://msdn.microsoft.com",
"https://msdn.microsoft.com/library/hh290136.aspx",
"https://msdn.microsoft.com/library/ee256749.aspx",
"https://msdn.microsoft.com/library/hh290138.aspx",
"https://msdn.microsoft.com/library/hh290140.aspx",
"https://msdn.microsoft.com/library/dd470362.aspx",
"https://msdn.microsoft.com/library/aa578028.aspx",
"https://msdn.microsoft.com/library/ms404677.aspx",
"https://msdn.microsoft.com/library/ff730837.aspx"
}
Return urls
End Function
Private Async Function GetURLContentsAsync(url As String) As Task(Of Byte())
' The downloaded resource ends up in the variable named content.
Dim content = New MemoryStream()
' Initialize an HttpWebRequest for the current URL.
Dim webReq = CType(WebRequest.Create(url), HttpWebRequest)
' Send the request to the Internet resource and wait for
' the response.
Using response As WebResponse = Await webReq.GetResponseAsync()
' The previous statement abbreviates the following two statements.
'Dim responseTask As Task(Of WebResponse) = webReq.GetResponseAsync()
'Using response As WebResponse = Await responseTask
' Get the data stream that is associated with the specified URL.
Using responseStream As Stream = response.GetResponseStream()
' Read the bytes in responseStream and copy them to content.
Await responseStream.CopyToAsync(content)
' The previous statement abbreviates the following two statements.
' CopyToAsync returns a Task, not a Task<T>.
'Dim copyTask As Task = responseStream.CopyToAsync(content)
' When copyTask is completed, content contains a copy of
' responseStream.
'Await copyTask
End Using
End Using
' Return the result as a byte array.
Return content.ToArray()
End Function
Private Sub DisplayResults(url As String, content As Byte())
' Display the length of each website. The string format
' is designed to be used with a monospaced font, such as
' Lucida Console or Global Monospace.
Dim bytes = content.Length
' Strip off the "https://".
Dim displayURL = url.Replace("https://", "")
resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes)
End Sub
End Class
下列程式碼包含使用 HttpClient 方法 GetByteArrayAsync 之方案的完整範例。
' Add the following Imports statements, and add a reference for System.Net.Http.
Imports System.Net.Http
Imports System.Net
Imports System.IO
Class MainWindow
Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click
resultsTextBox.Clear()
' Disable the button until the operation is complete.
startButton.IsEnabled = False
' One-step async call.
Await SumPageSizesAsync()
' Two-step async call.
'Dim sumTask As Task = SumPageSizesAsync()
'Await sumTask
resultsTextBox.Text &= vbCrLf & "Control returned to button1_Click."
' Reenable the button in case you want to run the operation again.
startButton.IsEnabled = True
End Sub
Private Async Function SumPageSizesAsync() As Task
' Declare an HttpClient object and increase the buffer size. The
' default buffer size is 65,536.
Dim client As HttpClient =
New HttpClient() With {.MaxResponseContentBufferSize = 1000000}
' Make a list of web addresses.
Dim urlList As List(Of String) = SetUpURLList()
Dim total = 0
For Each url In urlList
' GetByteArrayAsync returns a task. At completion, the task
' produces a byte array.
Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)
' The following two lines can replace the previous assignment statement.
'Dim getContentsTask As Task(Of Byte()) = client.GetByteArrayAsync(url)
'Dim urlContents As Byte() = Await getContentsTask
DisplayResults(url, urlContents)
' Update the total.
total += urlContents.Length
Next
' Display the total count for all of the websites.
resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf &
"Total bytes returned: {0}" & vbCrLf, total)
End Function
Private Function SetUpURLList() As List(Of String)
Dim urls = New List(Of String) From
{
"https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"https://msdn.microsoft.com",
"https://msdn.microsoft.com/library/hh290136.aspx",
"https://msdn.microsoft.com/library/ee256749.aspx",
"https://msdn.microsoft.com/library/hh290138.aspx",
"https://msdn.microsoft.com/library/hh290140.aspx",
"https://msdn.microsoft.com/library/dd470362.aspx",
"https://msdn.microsoft.com/library/aa578028.aspx",
"https://msdn.microsoft.com/library/ms404677.aspx",
"https://msdn.microsoft.com/library/ff730837.aspx"
}
Return urls
End Function
Private Sub DisplayResults(url As String, content As Byte())
' Display the length of each website. The string format
' is designed to be used with a monospaced font, such as
' Lucida Console or Global Monospace.
Dim bytes = content.Length
' Strip off the "https://".
Dim displayURL = url.Replace("https://", "")
resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes)
End Sub
End Class
另請參閱
- Async Sample: Accessing the Web Walkthrough (C# and Visual Basic) (非同步範例:存取 Web 逐步解說 (C# 和 Visual Basic))
- Await 運算子
- 非同步
- 使用 Async 和 Await 進行非同步程式設計 (Visual Basic)
- 非同步方法的傳回型別 (Visual Basic)
- Task-based Asynchronous Programming (TAP) (以工作為基礎的非同步程式設計 (TAP))
- 如何:使用 Task.WhenAll 擴充非同步逐步解說的內容 (Visual Basic)
- 如何:使用 Async 和 Await,同時發出多個 Web 要求 (Visual Basic)