Opanowanie Async i Await w C#
[[academy-video-youtube({"vid": "2moh18sh5p4", "start_time": "0", "title": "Mastering C# Async and Await", "creator": "Tim Corey", "length": "38m 57s"})]]
Programowanie asynchroniczne może wydawać się skomplikowane 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 zrozumienie 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. Slowa kluczowe async i await pozwalaja deweloperom pisac asynchroniczny kod, ktory moze wykonywac dlugotrwale zadania bez blokowania glownego watku. Oznaczajac metode slowem kluczowym async, wskazujesz, ze metoda bedzie wykonywac operacje asynchroniczna, zwracajac zadanie Task lub Task<t>. Slowo kluczowe await jest uzywane w asynchronicznej metodzie, aby wstrzymac wykonanie, az oczekiwane zadanie zostanie ukonczone, co pozwala na rownoczesne wykonywanie innych operacji.
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 artykule zbadamy, jak async i await wspolpracuja, omawiajac takie pojecia jak metody async void, obsluge bledow, przelaczniki kontekstu 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ą. Stopwatch jest uzywane, aby zmierzyc czas wykonywania 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ę nieodpowiedzialny, 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ą. To wymaga uzycia slow 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 owiniecie wywolania pobierania w
Task.Run, aby wykonac 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)); }- Slowo kluczowe
awaitzapewnia, ze metoda czeka na zakonczenie asynchronicznego zadania przed kontynuowaniem.
- Tim owiniecie wywolania pobierania w
Upewnij Się, że Metoda Jest Async:
- Sygnatura metody zostaje zaktualizowana o dodanie slowa kluczowego
asynci zwraca zadanie 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 zostaje zaktualizowana o dodanie slowa 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, ze uzywajac slowa kluczowego await, interfejs uzytkownika pozostaje responsywny. To pozwala uzytkownikom na interakcje z oknem, gdy asynchroniczne zadanie jest uruchomione.
// 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 upewnic sie, ze calkowity czas wykonania jest poprawnie raportowany, Tim dodaje slowo kluczowe await do wywolania w obslugiwaniu zdarzenia klikniecia 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 uzywana do oczekiwania na zakonczenie wszystkich zadan. 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}";
}Czekajac na RunDownloadParallelAsync, calkowity czas wykonania jest dokladnie 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.
Owijanie metody w Task.Run() vs Asynchroniczne wywolanie metody
Tim wyjasnia koncepcje uzycia Task.Run() do owiniecia synchronicznej metody i uczynienia jej asynchroniczna, gdy nie mozna zmodyfikowac 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, zmieniajac
DownloadWebsitenaDownloadWebsiteAsynci uzywajacDownloadStringTaskAsynczWebClient.
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 przeksztalceniu metody, kod wywolujacy musi byc dostosowany, aby usunac owiniecie
Task.Run()i bezposrednio wywolac metode asynchroniczna.
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 przeksztalceniu metody, kod wywolujacy musi byc dostosowany, aby usunac owiniecie
Podsumowanie Kluczowych Punktów
Tim kończy z kilkoma ważnymi wskazówkami dotyczącymi użycia async i await:
Zapewnij metody zwracajace zadania: Kiedykolwiek oznaczasz metode jako
async, powinna zwracacTasklubTask<t>zamiast void (z wyjatkiem obslug zdarzen).Uzyj
awaitdo niezawodnych operacji: Uzywaj slowa kluczowegoawait, gdy potrzebujesz wynikow asynchronicznej operacji przed kontynuowaniem.Owijanie zadania dla niemodyfikowalnego kodu: Uzyj
Task.Run()do owijania synchronicznych metod, gdy nie mozna zmodyfikowac oryginalnej metody.Odpowiednio oznacz metody async: Zawsze dodawaj
Asyncdo nazwy metody, aby wskazac, ze 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 podkresla, ze programowanie asynchroniczne w C# zostalo uproszczone dzieki 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 zrozumienie 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.

