C# AsyncとAwaitをマスターする
非同期プログラミングは、その複雑な用語で難しく思われるかもしれませんが、アプリケーションの高速化と応答性の向上に大きなメリットをもたらします。Tim Corey 氏のビデオ"C# Async / Await - Make your app more responsive and faster with asynchronous programming"では、これらの機能を効果的に使用するための実践的なガイドを提供しています。
この記事では、Tim Corey氏の詳細なビデオを使用して、これらの概念を分解し、明確な例を通してasyncとawaitの実用的なアプリケーションを示します。 非同期イベント・ハンドラから複数タスクの処理、同期コンテキスト、エラー処理、非同期voidメソッドの使用などのよくある落とし穴まで、さまざまなシナリオを取り上げます。 この記事を読み終わる頃には、非同期プログラミングと、それを自分のC#アプリケーションに実装する方法について、しっかりと理解していることでしょう。
はじめに
C#の非同期プログラミングは、アプリケーションのパフォーマンスを向上させるために不可欠であり、特にI/OバウンドやCPUバウンドの操作を行う場合に重要です。 awaitキーワードにより、開発者はメインスレッドをブロックせずに長時間実行するタスクを非同期で実行できるコードを書くことができます。 メソッドにTask<t>を返すことを示します。 非同期メソッド内でawaitキーワードを使用すると、待機中のタスクが完了するまで実行を一時停止し、他の操作を同時に実行できます。
タスクが順次実行され、呼び出し元のスレッドをブロックする可能性がある同期プログラミングとは異なり、非同期メソッドを使用すると、UIスレッドをフリーズさせることなく、バックグラウンド処理やユーザー入力の処理など、複数のタスクを同時に処理できます。 この記事では、awaitがどのように連携して動作するかを探り、async voidメソッド、エラー処理、コンテキストスイッチ、同期コンテキストなどの概念をカバーします。 複数の非同期処理を効率的に管理し、例外を処理して、呼び出しスレッド、UIスレッド、タスクの完了にわたってスムーズな実行を保証する方法を学びます。 このアプローチは、ファイルシステム、ネットワークリクエスト、同時リクエストなど、応答性が重要なシナリオに特に有効です。
Timは、同期操作と非同期操作の違いを説明することで、非同期プログラミングを紹介します。 同期プログラミングでは、タスクは順次実行されるため、タスクに時間がかかると遅延が発生します。非同期プログラミングでは、タスクを並列またはバックグラウンドで実行できるため、パフォーマンスと応答性が向上します。
非同期プログラミングの利点
ティムは、非同期プログラミングの2つの主な利点を強調します:
1.ユーザー インターフェイス (UI) の応答性の向上: タスクを非同期で実行することで、長時間実行される操作でも UI は応答性を維持します。
2.並列実行:独立したタスクを並列に実行できるため、完了までに必要な合計時間を短縮できます。
デモ アプリケーション ウォークスルー
Timは、同期操作と非同期操作の違いを示すために、デモのWindows Presentation Foundation(WPF)アプリケーションをセットアップします。 アプリケーションには、同期的にタスクを実行するボタンと、非同期的にタスクを実行するボタンの2つがあります。

このアプリケーションには、出力を表示する結果ペインも含まれています。
同期操作
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}";
}このコードは次の手順を実行します。
1.ストップウォッチを開始する:実行時間を測定します。 2.ウェブサイトリストを取得する:ウェブサイトのURLリストを取得します。 3.各ウェブサイトをダウンロードする:各ウェブサイトのコンテンツをダウンロードします。 4.レポートウェブサイト情報:ダウンロードしたコンテンツのURLと長さを表示します。 5.ストップウォッチを止める: タイマーを止め、合計実行時間を報告します。
同期操作の観察
Timがアプリケーションを実行して、同期動作のデモを行います。 彼は、ウェブサイトのダウンロード中にUIが反応しなくなり、ダウンロード完了後に結果が一度に表示されると指摘している。

フォームが動かないという実用的なデモについては、9:20からのビデオを見て理解を深めてください。
非同期タスクの作成
ティムは、同期メソッドを非同期メソッドに変換することで、最初の問題に取り組んでいます。 これはawaitキーワードを使用することを含みます。 以下は、プロセスのステップバイステップの説明です:
1.既存の同期メソッドをコピーする:
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 }
2.非同期実行のためのダウンロード呼び出しの修正:
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キーワードは、非同期タスクの完了を待ってからメソッドを続行することを保証します。
3.メソッドが非同期であることを確認してください:
メソッドのシグネチャは、
asyncキーワードを含めるように更新され、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); } }
4.イベントを正しく処理する:
ボタンクリックのイベントハンドラは、新しい非同期メソッドを呼び出すように更新されます。
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は、並列実行を使用することで、各タスクが順次完了するのを待つという制限に対処します。 そのために彼がどのようにコードを修正したかを紹介しよう:
1.既存の非同期メソッドをコピーする:
Tim は、既存の async メソッドを複製し、並列実行を示す名前に変更します。
private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here }private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here }
2.タスクのリストを作成する:
タスクのリストは、すべてのダウンロードタスクを保存するために作成されます。
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
3.待たずにすべてのタスクを開始する:
各ダウンロードタスクをすぐに待つのではなく、ティムはタスクリストに追加します。
foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); }foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); }
4.すべてのタスクが完了するのを待ちます:
Task.WhenAllメソッドは、すべてのタスクの完了を待機するために使用されます。 このメソッドは、すべてのタスクが完了すると、結果の配列を返します。WebsiteDataModel[] results = await Task.WhenAll(tasks);WebsiteDataModel[] results = await Task.WhenAll(tasks);
5.結果を処理する:
すべてのタスクが完了すると、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)); }
2.メソッドを非同期にする:
メソッドを修正できる場合は、適切な非同期APIを使用してメソッド自体を非同期に変換する方が良いでしょう。
Timは
DownloadStringTaskAsyncを使用することでこれを示しています。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 }; }
3.呼び出しメソッドの調整:
メソッドを変換した後、呼び出しコードを調整して、
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を使用するためのいくつかの重要なポイントについて締めくくります:
タスクを返すメソッドを確保する: メソッドを
Task<t>を返すようにするべきです(イベントハンドラを除く)。信頼できる操作のために
awaitを使用する: 処理を進める前に非同期操作の結果が必要なときにawaitキーワードを使用してください。非変更可能なコードのためのタスクラッピング: 元のメソッドを変更できないときに同期メソッドをラップするために
Task.Run()を使用します。非同期メソッドを適切にマークする: 非同期操作であることを示すために、常にメソッド名に
Asyncを付けてください。- 並列実行と逐次実行:タスク間の依存関係に基づいて、タスクを並列に実行できるか、お互いを待つべきかを決定します。
Timは、C#での非同期プログラミングがasync, await, およびTaskによってより簡単になっていることを強調しています。 スレッドコンテキストやアパートメントモデルなどの複雑な部分は、舞台裏で管理されます。
結論
Tim CoreyのC#におけるasyncとawaitのチュートリアルは、非同期プログラミングをマスターするための貴重なリソースを提供します。 タスクの並列処理、UIの応答性、適切なコンテキストでasyncとawaitを使用することの重要性などの概念を注意深く説明することで、Timは、より高速で応答性の高いアプリケーションを作成するためのツールを開発者に提供します。 彼の詳細なウォークスルーは、メインスレッドをブロックすることなく長時間実行するタスクを効率的に処理する方法や、非同期操作や並列実行を管理するための戦略を示しています。
より深い理解と追加の実用的な例については、ティム・コーリーのビデオを見ることを強くお勧めします。 非同期 void メソッド、エラー処理、同期コンテキストの管理に関する洞察は、プロジェクトでこれらの概念を実装する能力をさらに高めるでしょう。

