Opanowanie Async i Await w C#
Programowanie asynchroniczne może wydawać się skomplikówane z powodu złożonej terminologii, ale oferuje znaczne korzyści, czyniąc aplikacje szybszymi i bardziej responsywnymi. Film Tima Corey'a na temat "C# Async / Await - Sprawdź, jak zrobić swoją aplikację bardziej responsywną i szybszą dzięki programowaniu asynchronicznemu" oferuje praktyczny przewodnik po skutecznym wykorzystaniu tych funkcji.
W tym artykułe użyjemy dokładnego filmu Tima Corey'a do rozłożenia tych pojęć, prezentując praktyczne zastosowania async i await za pomocą jasnych przykładów. Zajmiemy się różnymi scenariuszami, od asynchronicznych obsług eventów po obsługę wielu zadań, konteksty synchronizacji, obsługę błędów i powszechne pułapki, jak użycie metod async void. Na końcu tego artykułu będziesz miał solidne zrozumieniuiuiuiuie programowania asynchronicznego i jak je wdrożyć w swoich aplikacjach w C#.
Wprowadzenie
Programowanie asynchroniczne w C# jest kluczowe dla poprawy wydajności aplikacji, szczególnie podczas pracy z operacjami intensywnie wykorzystującymi I/O lub CPU. Słowa kluczowe async i await pozwalają programistom pisać asynchroniczny kod, który może wykonywać długotrwałe zadania bez blokowania głównego wątku. Oznaczając metodę słowem kluczowym async, sygnalizujesz, że metoda wykona operację asynchroniczną, zwracając Task lub Task<t>. Słowo kluczowe await jest używane w asynchronicznej metodzie do wstrzymania wykonania do momentu zakończenia oczekiwanego zadania, pozwalając innym operacjom działać równocześnie.
W przeciwieństwie do programowania synchronicznego, w którym zadania wykonują się sekwencyjnie i mogą blokować wątek wywołujący, metody asynchroniczne umożliwiają obsługę wielu zadań jednocześnie, takich jak procesy w tle lub obsługa danych wejściowych od użytkownika, bez zamrażania wątku interfejsu użytkownika. W tym artykułe zbadamy, jak async i await współdziałają, obejmując pojęcia takie jak metody async void, obsługa błędów, przełączanie kontekstów i konteksty synchronizacji. Nauczysz się, jak efektywnie zarządzać wieloma operacjami asynchronicznymi i obsługiwać wyjątki, zapewniając płynne wykonanie przez wątek wywołujący, wątek interfejsu użytkownika i zakończenie zadania. To podejście jest szczególnie przydatne w scenariuszach obejmujących systemy plików, żądania sieciowe lub równoczesne żądania, gdzie kluczowa jest responsywność.
Tim wprowadza do programowania asynchronicznego, wyjaśniając różnicę pomiędzy operacjami synchronicznymi a asynchronicznymi. W programowaniu synchronicznym zadania wykonywane są sekwencyjnie, co powoduje opóźnienia, jeśli zadanie trwa długo. Programowanie asynchroniczne pozwala na wykonanie zadań równolegle lub w tle, poprawiając wydajność i responsywność.
Zalety Programowania Asynchronicznego
Tim wyróżnia dwie główne korzyści programowania asynchronicznego:
-
Poprawa responsywności interfejsu użytkownika (UI): Wykonywanie zadań asynchronicznie sprawia, że interfejs użytkownika pozostaje responsywny nawet podczas realizacji długotrwałych operacji.
- Równoległe wykonanie: Niezależne zadania mogą być wykonywane równolegle, zmniejszając całkowity czas potrzebny na ich zakończenie.
Demonstracja aplikacji - przewodnik
Tim ustawia demo aplikacji Windows Presentation Foundation (WPF), aby pokazać różnicę pomiędzy operacjami synchronicznymi a asynchronicznymi. Aplikacja zawiera dwa przyciski: jeden do wykonywania zadań synchronicznie, a drugi do ich wykonywania asynchronicznie.

Aplikacja zawiera również panel rezultatów do wyświetlania wyników.
Operacja Synchroniczna
Tim wyjaśnia kod stojący za operacją synchroniczną. Używany jest Stopwatch do zmierzenia czasu wykonania zadania.
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}";
}
Ten kod wykonuje następujące kroki:
- Uruchomienie Stopera: Mierzy czas wykonania.
- Pobranie Listy Stron Internetowych: Pobiera listę adresów URL stron internetowych.
- Pobierz Każdą Stronę Internetową: Pobiera zawartość każdej strony internetowej.
- Zgłoszenie Informacji o Stronie Internetowej: Wyświetla adres URL i długość pobranej zawartości.
- Zatrzymanie Stopera: Zatrzymuje stoper i raportuje całkowity czas wykonania.
Obserwowanie Operacji Synchronicznej
Tim uruchamia aplikację, aby pokazać operację synchroniczną. Zauważa, że interfejs użytkownika staje się nieodpowiedziąlny, gdy strony internetowe są pobierane, a wyniki są wyświetlane wszystkie naraz po zakończeniu pobierania.

Aby zobaczyć praktyczną demonstrację nieporuszającej się formy, obejrzyj film, aby lepiej zrozumieć o 9:20.
Tworzenie Zadania Async
Tim radzi sobie z pierwszym problemem, przekształcając metodę synchroniczną w asynchroniczną. Więc używa słów kluczowych async i await. Oto szczegółowy przedstawienie procesu:
-
Skopiowanie Istniejącej Metody Synchronicznej:
- Tim kopiuje istniejącą metodę synchroniczną i zmienia jej nazwę, aby wskazać, że będzie asynchroniczna.
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 } -
Modyfikacja Wywołania Pobierania dla Asynchronicznego Wykonania:
- Tim owija wywołanie pobierania w
Task.Run, aby wykonać je asynchronicznie.
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)); }- Słowo kluczowe
awaitzapewnia, że metoda czeka na zakończenie zadania asynchronicznego przed kontynuacją.
- Tim owija wywołanie pobierania w
-
Upewnij Się, że Metoda Jest Async:
- Sygnatura metody jest zaktualizowana o dodanie słowa kluczowego
async, a metoda zwraca Task.
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); } } - Sygnatura metody jest zaktualizowana o dodanie słowa kluczowego
-
Poprawna Obsługa Zdarzenia:
- Obsługa kliknięcia przycisku jest zaktualizowana, aby wywołać nową metodę asynchroniczną.
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}"; }- Uwaga, że obsługiwacze zdarzeń mogą zwracać void, nawet jeśli wywołują metody asynchroniczne.
Poprawa Responsywności interfejsu użytkownika
Tim pokazuje, że dzięki użyciu słowa kluczowego await UI pozostaje responsywne. Pozwala to użytkownikom na interakcję z oknem, podczas gdy zadanie asynchroniczne jest wykonywane.
// 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}";
}
Zapewnienie Poprawnego Pomiaru Czasu
Aby zapewnić, że całkowity czas wykonania jest poprawnie raportowany, Tim dodaje słowo kluczowe await do wywołania w obsłudze zdarzenia kliknięcia przycisku.
// 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}";
}
Czekając na zakończenie metody asynchronicznej, czas wykonania jest mierzony dokładnie, a wyniki są wyświetlane tak, jak są pobierane.
Tworzenie Równoległego Async
Tim odnosi się do ograniczenia czekania na zakończenie każdej z sekwencyjnie wykonywanych zadań, wykorzystując wykonanie równoległe. Oto jak zmodyfikował kod, aby to osiągnąć:
-
Skopiowanie Istniejącej Metody Async:
- Tim powiela istniejącą metodę async i zmienia jej nazwę, aby wskazać wykonanie równoległe.
private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here }private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here } -
Utworzenie Listy Zadań:
- Tworzona jest lista zadań do przechowywania wszystkich zadań pobierania.
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>(); -
Rozpoczęcie Wszystkich Zadań Bez Oczekiwania:
- Zamiast natychmiastowego oczekiwania na każde zadanie pobierania, Tim dodaje je do listy zadań.
foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); }foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); } -
Czekanie Na Zakończenie Wszystkich Zadań:
- Metoda
Task.WhenAlljest używana do oczekiwania na zakończenie wszystkich zadań. Metoda ta zwraca tablicę wyników, gdy wszystkie zadania są zakończone.
WebsiteDataModel[] results = await Task.WhenAll(tasks);WebsiteDataModel[] results = await Task.WhenAll(tasks); - Metoda
-
Przetwarzanie Wyników:
- Po zakończeniu wszystkich zadań Tim przetwarza wyniki w pętli.
foreach (var result in results) { ReportWebsiteInfo(result); }foreach (var result in results) { ReportWebsiteInfo(result); }
Oto kompletny kod dla metody wykonania równoległego:
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);
}
}
Aktualizacja Obsługi Zdarzenia
Tim aktualizuje obsługę kliknięcia przycisku, aby wywołać nową metodę wykonania równoległego.
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}";
}
Czekając na RunDownloadParallelAsync, całkowity czas wykonania jest precyzyjnie mierzony i raportowany.
Obserwowanie Poprawy Wydajności
Tim demonstruje poprawę wydajności, uruchamiając aplikację z wykonaniem równoległym. Wyniki pokazują znaczącą redukcję całkowitego czasu wykonania w porównaniu do wykonania sekwencyjnego.
// Parallel execution
async Task RunDownloadParallelAsync();
// Parallel execution
async Task RunDownloadParallelAsync();
Przyspieszenie jest widoczne, gdy strony internetowe są pobierane równocześnie, zmniejszając całkowity czas oczekiwania do czasu trwania najwolniejszego indywidualnego pobrania.
Opakowanie Metody w Task.Run() vs Wywołanie Metody Async
Tim wyjaśnia koncepcję użycia Task.Run() do owijania metody synchronicznej i uczynienia jej asynchroniczną, gdy nie możesz zmodyfikować oryginalnej metody. Jednakże, pokazuje również preferowane podejście modyfikacji samej metody, aby była asynchroniczna, jeśli masz nad nią kontrolę.
-
Owijanie Metody w
Task.Run():- To podejście jest użyteczne, gdy nie możesz zmienić kodu oryginalnej metody, ale nadal chcesz ją wykonać asynchronicznie.
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)); } -
Uczynienie Metody Asynchroniczną:
-
Jeśli możesz zmodyfikować metodę, lepiej jest przekonwertować samą metodę na asynchroniczną, używając odpowiedniego API asynchronicznego.
- Tim demonstruje to, zmieniając
DownloadWebsitenaDownloadWebsiteAsynci używającDownloadStringTaskAsynczWebClient.
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 }; } -
-
Dostosowanie Metody Wywołującej:
- Po przekonwertowaniu metody, kod wywołujący należy dostosować do usunięcia owijki
Task.Run()i bezpośredniego wywołania metody asynchronicznej.
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); } } - Po przekonwertowaniu metody, kod wywołujący należy dostosować do usunięcia owijki
Podsumowanie Kluczowych Punktów
Tim kończy z kilkoma ważnymi wskazówkami dotyczącymi użycia async i await:
-
Upewnij Się, że Metody Zwracają Zadania: Kiedykolwiek oznaczasz metodę jako
async, powinna zwracaćTasklubTask<t>zamiast void (z wyjątkiem obsługiwaczy zdarzeń). -
Użyj
awaitdo Niezawodnych Operacji: Użyj słowa kluczowegoawait, gdy potrzebujesz wyniku operacji asynchronicznej przed kontynuacją. -
Owijanie Zadań Dla Niedozmiennego Kodu: Użyj
Task.Run()do owijania metod synchronicznych, gdy nie możesz zmodyfikować oryginalnej metody. -
Oznaczaj Metody Asynchroniczne Odpowiednio: Zawsze dodawaj
Asyncdo nazwy metody, aby wskazać, że jest to operacja asynchroniczna. - Wykonanie Równoległe vs Sekwencyjne: Zdecyduj, czy zadania mogą być wykonywane równolegle, czy powinny czekać na siebie nawzajem, w oparciu o zależności między zadaniami.
Tim podkreśla, że programowanie asynchroniczne w C# stało się prostsze dzięki async, await i Task. Złożoności takie jak konteksty wątków i modele apartamentów są zarządzane w tle.
Wnioski
Samouczek Tima Corey'a na temat async i await w C# stanowi niezastąpione źródło do opanowania programowania asynchronicznego. Dokładnie wyjaśniając pojęcia takie jak równoległość zadań, responsywność interfejsu użytkownika oraz znaczenie używania async i await w odpowiednim kontekście, Tim dostarcza programistom narzędzi do tworzenia szybszych i bardziej responsywnych aplikacji. Jego szczegółowy przechodzenie pokazuje, jak efektywnie obsługiwać długotrwałe zadania bez blokowania głównego wątku, oraz strategie zarządzania operacjami asynchronicznymi i równoległym wykonaniem.
Aby uzyskać głębsze zrozumieniuiuiuiuie i dodatkowe praktyczne przykłady, gorąco polecam obejrzenie filmu Tima Corey'a video. Jego spostrzeżenia na temat metod async void, obsługi błędów i zarządzania kontekstami synchronizacji dodatkowo zwiększą twoją zdolność do wdrożenia tych pojęć w swoich projektach.
