掌握C#中的非同步和待處理
異步程式設計看似複雜,但提供了顯著的優勢,能使應用程式更快、更具回應性。Tim Corey在"C# Async / Await - 使用異步程式設計使您的應用程式更加回應迅速且更快"的影片中提供了使用這些功能的實用指南。
在本文中,我們將使用Tim Corey的詳細影片來分解這些概念,通過清晰的範例展示async和await的實際應用。 我們將涵蓋各種場景,從異步事件處理程式到處理多個任務、同步上下文、錯誤處理以及常見的陷阱如使用async void方法。 在本文結尾,您將對異步程式設計有一個堅實的理解,並知道如何將其實施到您自己的C#應用程式中去。
介紹
在C#中使用異步程式設計對於提高應用程式性能至關重要,尤其在處理I/O或CPU密集型操作時。 await關鍵詞允許開發人員撰寫異步程式碼,這些程式碼可以執行長時間運行的任務而不會阻塞主執行緒。 通過用Task<t>。 await關鍵詞用於異步方法內,以暫停執行直到等待的任務完成,允許其他操作同時運行。
與同步程式設計不同,任務按順序運行並可能阻塞正在呼叫的執行緒,異步方法可以讓您同時處理多個任務,例如後台進程或處理使用者輸入,而不會凍結UI執行緒。 在本文中,我們將探討await之間的協同工作,涉及的概念包括async void方法、錯誤處理、上下文切換和同步上下文。 您將學習如何有效管理多個異步操作並處理異常,確保在呼叫執行緒、UI執行緒和任務完成之間順暢執行。 這種方法在涉及檔案系統、網路請求或需要高響應的並發請求的情況下特別有用。
Tim通過解釋同步和異步操作之間的區別來介紹異步程式設計。 在同步程式設計中,任務按順序執行,如果任務耗時長會導致延遲。異步程式設計允許任務並行或在後台執行,從而提高性能和響應速度。
異步程式設計的優勢
Tim強調了異步程式設計的兩個主要優勢:
提高使用者介面(UI)的回應速度:通過異步執行任務,即使執行長時間運行的操作,UI仍保持回應。
- 平行執行:獨立任務可以並行執行,減少完成所需的總時間。
應用程式演示概述
Tim設置了一個演示Windows Presentation Foundation (WPF)應用程式來展示同步和異步操作之間的區別。 該應用程式有兩個按鈕:一個用於同步執行任務,另一個用於異步執行任務。

該應用程式還包括一個結果窗格來顯示輸出。
同步操作
Tim解釋了同步操作背後的程式碼。 用Stopwatch來測量任務的執行時間。
private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
List<string> websites = GetWebsiteList();
foreach (var website in websites)
{
string result = DownloadWebsite(website);
ReportWebsiteInfo(website, result);
}
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private List<string> GetWebsiteList()
{
return new List<string>
{
"https://www.yahoo.com",
"https://www.google.com",
"https://www.microsoft.com",
"https://www.cnn.com",
"https://www.codeproject.com",
"https://www.stackoverflow.com"
};
}
private string DownloadWebsite(string websiteURL)
{
WebClient client = new WebClient();
return client.DownloadString(websiteURL);
}
private void ReportWebsiteInfo(string website, string data)
{
ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
List<string> websites = GetWebsiteList();
foreach (var website in websites)
{
string result = DownloadWebsite(website);
ReportWebsiteInfo(website, result);
}
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private List<string> GetWebsiteList()
{
return new List<string>
{
"https://www.yahoo.com",
"https://www.google.com",
"https://www.microsoft.com",
"https://www.cnn.com",
"https://www.codeproject.com",
"https://www.stackoverflow.com"
};
}
private string DownloadWebsite(string websiteURL)
{
WebClient client = new WebClient();
return client.DownloadString(websiteURL);
}
private void ReportWebsiteInfo(string website, string data)
{
ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}該程式碼執行以下步驟:
- 開啟碼表:測量執行時間。
- 獲取網站列表:檢索網站URL列表。
- 下載每個網站:下載每個網站的內容。
- 報告網站資訊:顯示URL和下載內容的長度。
- 停止碼表:停止計時器並報告總執行時間。
觀察同步操作
Tim運行應用程式以展示同步操作。 他指出,在下載網站時UI變得無回應,下載完成後結果一次性顯示。

為了實際展示表格不移動的情況,請在9:20觀看影片以便更好地理解。
建立異步任務
Tim通過將同步方法轉換為異步方法來解決第一個問題。 這包括使用await關鍵詞。 以下是過程的逐步解釋:
複製現有同步方法:
- Tim複製現有的同步方法,並重新命名以表明它將是異步的。
private async Task RunDownloadAsync() { // Same code as RunDownloadSync, but will be modified for async }private async Task RunDownloadAsync() { // Same code as RunDownloadSync, but will be modified for async }修改下載調用以進行異步執行:
- Tim將下載調用包裝在
Task.Run中,以異步執行它。
private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); }private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); }await關鍵字確保在繼續之前,該方法等待異步任務完成。
- Tim將下載調用包裝在
確保方法是異步的:
- 更新方法的簽名以包含
async這一關鍵字,並返回一個任務。
private async Task RunDownloadAsync() { List<string> websites = GetWebsiteList(); foreach (var website in websites) { var result = await DownloadWebsiteAsync(website); ReportWebsiteInfo(website, result); } }private async Task RunDownloadAsync() { List<string> websites = GetWebsiteList(); foreach (var website in websites) { var result = await DownloadWebsiteAsync(website); ReportWebsiteInfo(website, result); } }- 更新方法的簽名以包含
正確地處理事件:
- 更新按鈕點擊事件處理程式以調用新的異步方法。
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e) { Stopwatch stopwatch = Stopwatch.StartNew(); await RunDownloadAsync(); stopwatch.Stop(); ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}"; }private async void ExecuteAsync_Click(object sender, RoutedEventArgs e) { Stopwatch stopwatch = Stopwatch.StartNew(); await RunDownloadAsync(); stopwatch.Stop(); ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}"; }- 注意,事件處理器可以返回void,即使它們調用異步方法。
解決UI回應問題
Tim展示了使用await關鍵詞後,UI保持回應性。這允許使用者在異步任務運行期間與窗口交互。
// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}確保正確的時機
為了確保正確報告總執行時間,Tim在按鈕點擊事件處理程式的調用中新增了await關鍵詞。
// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}通過等待異步方法的完成,執行時間得以準確測量,並且結果在檢索時顯示。
建立平行異步
Tim透過使用平行執行來解決等待每個任務依序完成的限制。 以下是他如何修改程式碼以達到此目的:
複製現有的異步方法:
- Tim複製現有的異步方法並重新命名以表示平行執行。
private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here }private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here }建立任務列表:
- 建立一個任務列表來儲存所有下載任務。
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();發起所有任務而不等待:
- Tim將每個下載任務加到任務列表中,而不是立即等待它們。
foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); }foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); }等待所有任務完成:
- 使用
Task.WhenAll方法等待所有任務完成。 此方法在所有任務完成後返回一個結果陣列。
WebsiteDataModel[] results = await Task.WhenAll(tasks);WebsiteDataModel[] results = await Task.WhenAll(tasks);- 使用
處理結果:
- 在所有任務完成後,Tim在迴圈中處理結果。
foreach (var result in results) { ReportWebsiteInfo(result); }foreach (var result in results) { ReportWebsiteInfo(result); }
以下是平行執行方法的完整程式碼:
private async Task RunDownloadParallelAsync()
{
List<string> websites = GetWebsiteList();
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
foreach (var website in websites)
{
tasks.Add(DownloadWebsiteAsync(website));
}
WebsiteDataModel[] results = await Task.WhenAll(tasks);
foreach (var result in results)
{
ReportWebsiteInfo(result);
}
}private async Task RunDownloadParallelAsync()
{
List<string> websites = GetWebsiteList();
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
foreach (var website in websites)
{
tasks.Add(DownloadWebsiteAsync(website));
}
WebsiteDataModel[] results = await Task.WhenAll(tasks);
foreach (var result in results)
{
ReportWebsiteInfo(result);
}
}更新事件處理器
Tim會更新按鈕點擊事件處理器以呼叫新的平行執行方法。
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadParallelAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadParallelAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}通過等待RunDownloadParallelAsync,準確測量並報告總執行時間。
觀察性能提升
Tim透過執行具平行執行的應用程式來展示性能的提升。 結果顯示,與順序執行相比,總執行時間顯著減少。
// Parallel execution
async Task RunDownloadParallelAsync();// Parallel execution
async Task RunDownloadParallelAsync();速度提升顯著,網站同時下載,將總等待時間縮短至最慢個別下載的時長。
將方法包裝在Task.Run()中 vs 異步方法調用
Tim解釋了使用Task.Run()來包裝同步方法並使其異步的概念,當你無法修改原始方法時使用。 然而,他也展示了在你有權限的情況下,修改方法本身為異步的最佳方法。
將方法包裝在
Task.Run()中:- 當不能更改原始方法的程式碼但仍需異步執行時,此方法很有用。
private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); }private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); }使方法異步化:
如果您可以修改方法,最好使用適當的異步API將方法本身轉換為異步。
- Tim通過將
WebClient演示了這一點。
private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { WebClient client = new WebClient(); string data = await client.DownloadStringTaskAsync(websiteURL); return new WebsiteDataModel { URL = websiteURL, Data = data }; }private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { WebClient client = new WebClient(); string data = await client.DownloadStringTaskAsync(websiteURL); return new WebsiteDataModel { URL = websiteURL, Data = data }; }調整呼叫方法:
- 在轉換方法後,調用程式碼需要調整以移除
Task.Run()包裝,直接調用異步方法。
private async Task RunDownloadParallelAsync() { List<string> websites = GetWebsiteList(); List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>(); foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); } WebsiteDataModel[] results = await Task.WhenAll(tasks); foreach (var result in results) { ReportWebsiteInfo(result); } }private async Task RunDownloadParallelAsync() { List<string> websites = GetWebsiteList(); List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>(); foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); } WebsiteDataModel[] results = await Task.WhenAll(tasks); foreach (var result in results) { ReportWebsiteInfo(result); } }- 在轉換方法後,調用程式碼需要調整以移除
要點總結
Tim收尾時指出了使用async和await的幾個重要要點:
— 確保方法返回Tasks:當您標示一個方法為Task<t>,而不是void(事件處理器除外)。
— 使用await關鍵字。
— 對無法修改程式碼進行任務包裝:當您無法修改原始方法時,使用Task.Run()來包裝同步方法。
— 正確標示異步方法:始終在方法名稱中新增Async以指明它是異步操作。
— 平行與順序執行:根據任務之間的依賴性決定任務是可以並行執行還是應等待彼此。
Tim強調,在C#中使用Task使異步程式設計變得更簡單。 像執行緒上下文和公寓模型等複雜性在後台已被處理。
結論
Tim Corey對C#中的async和await的教程提供了一個寶貴的資源,用於掌握異步程式設計。 通過仔細解釋任務平行性、UI反應性以及在正確的情境中使用async和await的重要性,Tim為開發人員提供了建立更快及更具回應性的應用程式的工具。 他的詳細演示展示了如何高效處理長時間運行的任務而不阻塞主執行緒,以及管理異步操作和平行執行的策略。
如需更深入的理解和更多實用範例,我強烈推薦觀看Tim Corey的影片。 他對async void方法、錯誤處理以及管理同步上下文的見解將進一步提升您在專案中實施這些概念的能力。

