異步對可能起阻止作用的活動(例如,應用程序訪問 Web 時)至關重要。 對 Web 資源的訪問有時很慢或會延遲。 如果此類活動在同步過程中受阻,則整個應用程序必須等待。在異步過程中,應用程序可繼續執行不依賴 Web 資源的其他工作,直至潛在阻止任務完成。
下表是利用異步編程能提高響應能力的典型場景。從 .NET Framework 4.5 和 Windows 運行時中列出的 API 包含支持異步編程的方法。
應用程序區域 | 包含異步方法的受支持的 API |
Web 訪問 | HttpClient ,SyndicationClient |
使用文件 | StorageFile、StreamWriter、StreamReader、xmlReader |
使用圖像 | MediaCapture、BitmapEncoder、BitmapDecoder |
WCF 編程 | 同步和異步操作 |
由于所有與用戶界面相關的活動通常共享一個線程,因此,異步對訪問 UI 線程的應用程序來說尤為重要。如果任何進程在同步應用程序中受阻,則所有進程都將受阻。 你的應用程序停止響應,因此,你可能在其等待過程中認為它已經失敗。
使用異步方法時,應用程序將繼續響應 UI。 例如,你可以調整窗口的大小或最小化窗口;如果你不希望等待應用程序結束,則可以將其關閉。
可以使用三種方式來實現 TAP:即手動使用 C# 編譯器,或將編譯器和手動方法結合使用。使用 TAP 模式來實現計算密集型和 I/O 密集型異步操作。
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
}
PRivate async Task<int> MethodAsyncInternal(string input)
{
// code that uses await goes here
return value;
}
本文主要說明“使用編譯器”方法。
C# 中 async 和 await 關鍵字是異步編程的核心。通過這兩個關鍵字就可以輕松創建異步方法,幾乎與創建同步方法一樣。如下所示的 WPF 程序,布局文件上有個按鈕和文本框:
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
// Call and await separately.
//Task<int> getLengthTask = accessTheWebAsync();
//// You can do independent work here.
//int contentLength = await getLengthTask;
int contentLength = await AccessTheWebAsync();
resultsTextBox.Text +=
String.Format("/r/nLength of the downloaded string: {0}./r/n", contentLength);
}
// Three things to note in the signature:
// - The method has an async modifier.
// - The return type is Task or Task<T>. (See "Return Types" section.)
// Here, it is Task<int> because the return statement returns an integer.
// - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{
// You need to add a reference to System.Net.Http to declare client.
HttpClient client = new HttpClient();
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
// The await Operator suspends AccessTheWebAsync.
// - AccessTheWebAsync can't continue until getStringTask is complete.
// - Meanwhile, control returns to the caller of AccessTheWebAsync.
// - Control resumes here when getStringTask is complete.
// - The await operator then retrieves the string result from getStringTask.
string urlContents = await getStringTask;
// The return statement specifies an integer result.
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
return urlContents.Length;
}
void DoIndependentWork()
{
resultsTextBox.Text += "Working . . . . . . ./r/n";
}
執行結果:
Working . . . . . . .
Length of the downloaded string: 41609.
說明:
1,當程序訪問網絡時,無論你如何拖拽、最大化最小化、如何點擊,UI 都不會失去響應;
2,“async Task<int> AccessTheWebAsync()”方法簽名,有三點需要注意:1)有 async 修飾符;2)返回類型是 Task 或 Task<int>。該方法是 Task<int>,因為它返回的是鏈接內容的大??;3)方法名以 Async 結尾;
3,“string urlContents = await getStringTask;”語句,有四點需要注意:1)AccessTheWebAsync 方法直到 getStringTask 完成才能繼續;2)同時,控制流返回到 AccessTheWebAsync 的調用者;3)getStringTask 完成后,控制流才會恢復;4)之后,await 操作符從 getStringTask 檢索結果。
下面總結讓一個示例成為異步方法的特征:
在異步方法中,可使用提供的關鍵字和類型來指示需要完成的操作,且編譯器會完成其余操作,其中包括持續跟蹤控件以掛起方法返回等待點時發生的情況。 一些常規流程(例如,循環和異常處理)在傳統異步代碼中處理起來可能很困難。 在異步方法中,元素的編寫頻率與同步解決方案相同且此問題得到解決。
異步編程中最需弄清的是控制流是如何從方法移動到方法。
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
// Call and await separately.
//Task<int> getLengthTask = AccessTheWebAsync();
//// You can do independent work here.
//int contentLength = await getLengthTask;
resultsTextBox.Text += "1: Entering startButton_Click./r/n" +
" Calling AccessTheWebAsync./r/n";
int contentLength = await AccessTheWebAsync();
resultsTextBox.Text +=
String.Format("/r/n6: Length of the downloaded string: {0}./r/n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
resultsTextBox.Text += "/r/n2: Entering AccessTheWebAsync.";
HttpClient client = new HttpClient();
resultsTextBox.Text += "/r/n Calling HttpClient.GetStringAsync./r/n";
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
DoIndependentWork();
resultsTextBox.Text += "/r/n4: Back in startButton_Click./r/n" +
" Task getStringTask is started./r/n";
string urlContents = await getStringTask;
resultsTextBox.Text += "/r/n5: Back in AccessTheWebAsync." +
"/r/n Task getStringTask is complete." +
"/r/n Processing the return statement." +
"/r/n Exiting from AccessTheWebAsync./r/n";
return urlContents.Length;
}
void DoIndependentWork()
{
resultsTextBox.Text += "/r/n3: Entering DoIndependentWork./r/n";
resultsTextBox.Text += "/r/n Working . . . . . . ./r/n";
}
運行結果:
1: Entering startButton_Click.
Calling AccessTheWebAsync.
2: Entering AccessTheWebAsync.
Calling HttpClient.GetStringAsync.
3: Entering DoIndependentWork.
Working . . . . . . .
4: Back in startButton_Click.
Task getStringTask is started.
5: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
6: Length of the downloaded string: 41609.
再稍微復雜點:
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// The display lines in the example lead you through the control shifts.
resultsTextBox.Text += "ONE: Entering startButton_Click./r/n" +
" Calling AccessTheWebAsync./r/n";
Task<int> getLengthTask = AccessTheWebAsync();
resultsTextBox.Text += "/r/nFOUR: Back in startButton_Click./r/n" +
" Task getLengthTask is started./r/n" +
" About to await getLengthTask -- no caller to return to./r/n";
int contentLength = await getLengthTask;
resultsTextBox.Text += "/r/nSIX: Back in startButton_Click./r/n" +
" Task getLengthTask is finished./r/n" +
" Result from AccessTheWebAsync is stored in contentLength./r/n" +
" About to display contentLength and exit./r/n";
resultsTextBox.Text +=
String.Format("/r/nLength of the downloaded string: {0}./r/n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
resultsTextBox.Text += "/r/nTWO: Entering AccessTheWebAsync.";
// Declare an HttpClient object and increase the buffer size. The default
// buffer size is 65,536.
HttpClient client =
new HttpClient() { MaxResponseContentBufferSize = 1000000 };
resultsTextBox.Text += "/r/n Calling HttpClient.GetStringAsync./r/n";
// GetStringAsync returns a Task<string>.
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
resultsTextBox.Text += "/r/nTHREE: Back in AccessTheWebAsync./r/n" +
" Task getStringTask is started.";
// AccessTheWebAsync can continue to work until getStringTask is awaited.
resultsTextBox.Text +=
"/r/n About to await getStringTask and return a Task<int> to startButton_Click./r/n";
// Retrieve the website contents when task is complete.
string urlContents = await getStringTask;
resultsTextBox.Text += "/r/nFIVE: Back in AccessTheWebAsync." +
"/r/n Task getStringTask is complete." +
"/r/n Processing the return statement." +
"/r/n Exiting from AccessTheWebAsync./r/n";
return urlContents.Length;
}
運行結果:
ONE: Entering startButton_Click.
Calling AccessTheWebAsync.
TWO: Entering AccessTheWebAsync.
Calling HttpClient.GetStringAsync.
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask and return a Task<;int> to startButton_Click.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Length of the downloaded string: 41635.
如何找到像 GetStringAsync 這樣支持異步編程的方法。 .NET Framework 4.5 包含使用 async 和 await 的許多成員,它們都已“Async”為后綴和 Task 或 Task<TResult> 的返回類型。 例如,System.IO.Stream 類包含的方法 CopyToAsync、ReadAsync、WriteAsync 等方法以及同步方法 CopyTo、Read 和 Write。
異步方法旨在成為非阻止操作。異步方法中的 await 表達式在等待的任務正在運行時,不會阻止當前線程。相反,表達式在繼續時,注冊方法的其余部分并將控件返回到異步方法的調用方。
async 和 await 關鍵字不會導致創建其他線程。因為異步方法不會在其自身線程上運行,因此它不需要多線程。 只有當方法處于活動狀態時,該方法將在當前同步上下文中運行并使用線程上的時間。 可以使用 Task.Run 將占用大量 CPU 的工作移到后臺線程,但是后臺線程不會幫助正在等待結果的進程變為可用狀態。
對于異步編程而言,該基于異步的方法優于幾乎每個用例中的現有方法。 具體而言,此方法比 BackgroundWorker 更適用于 IO 綁定的操作,因為此代碼更簡單且無需防止爭用條件。 結合 Task.Run 使用時,異步編程比 BackgroundWorker 更適用于 CPU 綁定的操作,因為異步編程將運行代碼的協調細節與 Task.Run 傳輸至線程池的工作區分開來。
如果通過 async 修飾符指定某種方法為異步方法,則可以啟用以下兩個功能。
異步方法通常包含 await 運算符的一個或多個匹配項,但缺少 await 表達式不會導致編譯器錯誤。 如果異步方法未使用 await 運算符標記懸掛點,則該方法將作為同步方法執行,不管異步修飾符如何。編譯器將為此類方法發布一個警告。
Async 、async、Await 和 await 都是上下文關鍵字。 有關更多信息和示例,請參見以下主題:
.NET Framework 異步編程中異步方法通常返回 Task 或 Task<TResult>。 在異步方法中,await 運算符應用于通過調用另一個異步方法返回的任務。
如果方法包含 Return (Visual Basic) 或指定類型 TResult 的操作數的 return (C#) 語句,則將 Task<TResult> 指定為返回類型。
如果方法不含任何 return 語句或包含不返回操作數的 return 語句,則將 Task 用作返回類型。
下面的示例演示如何聲明并調用可返回 Task<TResult> 或 Task 的方法。
// Signature specifies Task<;TResult>
async Task<;int> TaskOfTResult_MethodAsync()
{
int hours;
// . . .
// Return statement specifies an integer result.
return hours;
}
// Calls to TaskOfTResult_MethodAsync
Task<;int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();
// Signature specifies Task
async Task Task_MethodAsync()
{
// . . .
// The method has no return statement.
}
// Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync();
await returnedTask;
// or, in a single statement
await Task_MethodAsync();
每個返回的任務表示正在進行的工作。 任務可封裝有關異步進程狀態的信息,如果未成功,則最后會封裝來自進程的最終結果或進程引發的異常。
異步方法還可以是 Sub 方法 (Visual Basic) 或具有 void 返回類型 (C#)。 該返回類型主要用于定義需要 void 返回類型的事件處理程序。 異步事件處理程序通常用作異步程序的起始點。
無法等待為 Sub 程序或具有 void 返回類型的異步方法,并且無效的返回方法的調用方無法捕獲該方法引發的任何異常。
異步方法無法聲明 Visual Basic 中的 ByRef 參數或 C# 中的 ref 或 out 參數,但此方法可以調用具有此類參數的方法。
有關更多信息和示例,請參見異步返回類型(C# 和 Visual Basic)。 有關如何在異步方法中捕捉異常的更多信息,請參見 try-catch(C# 參考)或 Try...Catch...Finally 語句 (Visual Basic)。
Windows 運行時編程中的異步 API 具有下列返回類型之一,它類似于任務:
下載 Demo
下載 Demo TPL 與 AMP 和 EAP 結合
新聞熱點
疑難解答