[C#] awaitステートメントの使い方 – 非同期処理を待機する
C#のawait
ステートメントは、非同期メソッドの完了を待機するために使用されます。
await
は、async修飾子
が付いたメソッド内でのみ使用可能です。
非同期メソッドは通常、Task
またはTask<T>
を返し、await
を使うことで、処理が完了するまで他の処理をブロックせずに待機できます。
例えば、await SomeAsyncMethod()
と記述することで、SomeAsyncMethod
の完了を待ちながら、他のタスクを並行して実行できます。
- awaitステートメントの基本的な使い方
- 非同期処理の実践例と応用
- デッドロックやブロックの回避方法
- 非同期メソッドの例外処理の重要性
- タイムアウトやキャンセルの設定方法
非同期処理を活用し、アプリケーションのパフォーマンス向上を目指すことが重要。
awaitステートメントの基本的な使い方
非同期メソッドの定義
C#における非同期メソッドは、async
キーワードを使用して定義されます。
非同期メソッドは、通常のメソッドと同様に引数を受け取りますが、戻り値はTask
またはTask<T>
である必要があります。
これにより、メソッドが非同期に実行されることを示します。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await SampleAsyncMethod();
}
static async Task SampleAsyncMethod()
{
// 非同期処理をここに記述
await Task.Delay(1000); // 1秒待機
Console.WriteLine("非同期処理が完了しました。");
}
}
非同期処理が完了しました。
TaskとTask<T>の違い
Task
とTask<T>
は、非同期メソッドの戻り値として使用される2つの異なる型です。
型 | 説明 |
---|---|
Task | 戻り値がない非同期メソッドに使用 |
Task<T> | 戻り値がある非同期メソッドに使用 |
Task<T>
は、非同期処理の結果を返すために使用され、T
は戻り値の型を示します。
awaitを使った非同期メソッドの呼び出し
await
キーワードを使用することで、非同期メソッドの完了を待機することができます。
await
は、非同期メソッドの実行を一時停止し、結果が得られるまで待機します。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
int result = await CalculateAsync(5);
Console.WriteLine($"計算結果: {result}");
}
static async Task<int> CalculateAsync(int number)
{
await Task.Delay(1000); // 1秒待機
return number * number; // 数の二乗を返す
}
}
計算結果: 25
戻り値がない非同期メソッドの扱い
戻り値がない非同期メソッドは、Task
を返す必要があります。
await
を使用して呼び出す際には、戻り値を受け取る必要はありません。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await PrintMessageAsync();
}
static async Task PrintMessageAsync()
{
await Task.Delay(1000); // 1秒待機
Console.WriteLine("メッセージを表示しました。");
}
}
メッセージを表示しました。
例外処理とawait
非同期メソッド内で発生した例外は、await
を使用して呼び出す際にキャッチすることができます。
try-catch
ブロックを使用して、例外を適切に処理することが重要です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
try
{
await ThrowExceptionAsync();
}
catch (Exception ex)
{
Console.WriteLine($"例外が発生しました: {ex.Message}");
}
}
static async Task ThrowExceptionAsync()
{
await Task.Delay(1000); // 1秒待機
throw new InvalidOperationException("無効な操作です。");
}
}
例外が発生しました: 無効な操作です。
awaitの動作の仕組み
awaitの内部動作
await
キーワードは、非同期メソッドの実行を一時停止し、指定されたTask
が完了するのを待機します。
await
が呼び出されると、現在のメソッドの状態が保存され、制御が呼び出し元に戻ります。
Task
が完了すると、保存された状態が復元され、メソッドの実行が再開されます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("処理開始");
await SampleAsyncMethod();
Console.WriteLine("処理完了");
}
static async Task SampleAsyncMethod()
{
await Task.Delay(2000); // 2秒待機
Console.WriteLine("非同期処理が完了しました。");
}
}
処理開始
非同期処理が完了しました。
処理完了
コンテキストの切り替え
await
を使用すると、非同期メソッドの実行が一時停止し、呼び出し元のコンテキストに戻ります。
デフォルトでは、await
は元のコンテキスト(例えば、UIスレッド)で続行されますが、ConfigureAwait(false)
を使用することで、コンテキストを無視してバックグラウンドスレッドで実行することも可能です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("処理開始");
await SampleAsyncMethod().ConfigureAwait(false);
Console.WriteLine("処理完了");
}
static async Task SampleAsyncMethod()
{
await Task.Delay(2000); // 2秒待機
Console.WriteLine("非同期処理が完了しました。");
}
}
処理開始
非同期処理が完了しました。
処理完了
スレッドのブロックと非ブロック
await
を使用することで、スレッドをブロックすることなく非同期処理を実行できます。
これにより、UIスレッドがブロックされず、アプリケーションが応答し続けることが可能になります。
非同期メソッドは、I/O操作や長時間の計算を行う際に特に有効です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("処理開始");
await LongRunningOperationAsync();
Console.WriteLine("処理完了");
}
static async Task LongRunningOperationAsync()
{
await Task.Delay(5000); // 5秒待機
Console.WriteLine("長時間処理が完了しました。");
}
}
処理開始
長時間処理が完了しました。
処理完了
awaitのパフォーマンスへの影響
await
を使用することで、アプリケーションのパフォーマンスが向上する場合があります。
特に、I/Oバウンドの操作(ファイルの読み書きやネットワーク通信など)では、await
を使用することで、スレッドを無駄に消費せずに効率的に処理を行うことができます。
ただし、CPUバウンドの操作では、await
の使用が必ずしもパフォーマンス向上につながるわけではありません。
using System;
using System.Diagnostics;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
await PerformIOOperationAsync();
stopwatch.Stop();
Console.WriteLine($"処理時間: {stopwatch.ElapsedMilliseconds} ms");
}
static async Task PerformIOOperationAsync()
{
await Task.Delay(3000); // 3秒待機
Console.WriteLine("I/O操作が完了しました。");
}
}
I/O操作が完了しました。
処理時間: 3000 ms
非同期処理の実践例
ファイルの読み書きでのawaitの使用
非同期でファイルの読み書きを行う際には、FileStream
やStreamReader
、StreamWriter
などのクラスを使用します。
await
を使うことで、ファイル操作中にアプリケーションが応答し続けることができます。
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string filePath = "sample.txt";
await WriteToFileAsync(filePath, "こんにちは、非同期ファイル操作!");
string content = await ReadFromFileAsync(filePath);
Console.WriteLine($"ファイルの内容: {content}");
}
static async Task WriteToFileAsync(string path, string content)
{
using (StreamWriter writer = new StreamWriter(path))
{
await writer.WriteLineAsync(content); // 非同期で書き込み
}
}
static async Task<string> ReadFromFileAsync(string path)
{
using (StreamReader reader = new StreamReader(path))
{
return await reader.ReadToEndAsync(); // 非同期で読み込み
}
}
}
ファイルの内容: こんにちは、非同期ファイル操作!
Web API呼び出しでのawaitの使用
非同期でWeb APIを呼び出す際には、HttpClientクラス
を使用します。
await
を使うことで、APIからの応答を待機しながら、他の処理を行うことができます。
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string url = "https://api.github.com/users/octocat";
string response = await GetApiResponseAsync(url);
Console.WriteLine($"APIの応答: {response}");
}
static async Task<string> GetApiResponseAsync(string url)
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3");
return await client.GetStringAsync(url); // 非同期でAPI呼び出し
}
}
}
APIの応答: { ... } // GitHub APIからのJSON応答
データベースアクセスでのawaitの使用
非同期でデータベースにアクセスする際には、Entity FrameworkやDapperなどのORMを使用します。
await
を使うことで、データベース操作中にアプリケーションが応答し続けることができます。
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string connectionString = "your_connection_string_here";
var users = await GetUsersAsync(connectionString);
foreach (var user in users)
{
Console.WriteLine($"ユーザー名: {user}");
}
}
static async Task<List<string>> GetUsersAsync(string connectionString)
{
var users = new List<string>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync(); // 非同期で接続
using (SqlCommand command = new SqlCommand("SELECT Name FROM Users", connection))
{
using (SqlDataReader reader = await command.ExecuteReaderAsync()) // 非同期でクエリ実行
{
while (await reader.ReadAsync()) // 非同期で読み込み
{
users.Add(reader.GetString(0));
}
}
}
}
return users;
}
}
ユーザー名: ユーザー1
ユーザー名: ユーザー2
UIスレッドでのawaitの使用
UIアプリケーションでは、await
を使用することで、UIスレッドをブロックせずに非同期処理を実行できます。
これにより、ユーザーインターフェースが応答し続けることができます。
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
class Program : Form
{
private Button button;
public Program()
{
button = new Button { Text = "非同期処理を実行" };
button.Click += async (sender, e) => await PerformAsyncOperation();
Controls.Add(button);
}
static async Task Main()
{
Application.Run(new Program());
}
private async Task PerformAsyncOperation()
{
button.Enabled = false; // ボタンを無効化
await Task.Delay(3000); // 3秒待機
MessageBox.Show("非同期処理が完了しました。");
button.Enabled = true; // ボタンを再度有効化
}
}
非同期処理が完了しました。 // メッセージボックスが表示される
awaitの応用
複数の非同期処理を同時に実行する
複数の非同期処理を同時に実行することで、全体の処理時間を短縮できます。
Task
をリストに追加し、await
を使ってそれらを同時に実行します。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var tasks = new List<Task>
{
PerformTaskAsync("タスク1", 2000),
PerformTaskAsync("タスク2", 3000),
PerformTaskAsync("タスク3", 1000)
};
await Task.WhenAll(tasks); // 全てのタスクが完了するのを待機
Console.WriteLine("全てのタスクが完了しました。");
}
static async Task PerformTaskAsync(string taskName, int delay)
{
await Task.Delay(delay); // 指定された時間待機
Console.WriteLine($"{taskName} が完了しました。");
}
}
タスク3 が完了しました。
タスク1 が完了しました。
タスク2 が完了しました。
全てのタスクが完了しました。
Task.WhenAllとTask.WhenAnyの使い方
Task.WhenAll
は、すべてのタスクが完了するのを待機します。
一方、Task.WhenAny
は、最初に完了したタスクを待機します。
これにより、特定の条件に基づいて処理を行うことができます。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var tasks = new List<Task>
{
PerformTaskAsync("タスク1", 3000),
PerformTaskAsync("タスク2", 2000),
PerformTaskAsync("タスク3", 1000)
};
Task firstCompletedTask = await Task.WhenAny(tasks); // 最初に完了したタスクを待機
Console.WriteLine("最初に完了したタスクが見つかりました。");
}
static async Task PerformTaskAsync(string taskName, int delay)
{
await Task.Delay(delay); // 指定された時間待機
Console.WriteLine($"{taskName} が完了しました。");
}
}
タスク3 が完了しました。
最初に完了したタスクが見つかりました。
非同期処理のキャンセル
非同期処理をキャンセルするには、CancellationToken
を使用します。
これにより、長時間実行される処理を中断することができます。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
var token = cts.Token;
var task = PerformCancellableTaskAsync(token);
// 2秒後にキャンセル
cts.CancelAfter(2000);
try
{
await task; // タスクの完了を待機
}
catch (OperationCanceledException)
{
Console.WriteLine("タスクがキャンセルされました。");
}
}
static async Task PerformCancellableTaskAsync(CancellationToken token)
{
for (int i = 0; i < 5; i++)
{
token.ThrowIfCancellationRequested(); // キャンセル要求をチェック
await Task.Delay(1000); // 1秒待機
Console.WriteLine($"処理中... {i + 1}");
}
}
}
処理中... 1
処理中... 2
タスクがキャンセルされました。
非同期処理のタイムアウト設定
非同期処理にタイムアウトを設定することで、指定した時間内に処理が完了しない場合に例外を発生させることができます。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
cts.CancelAfter(2000); // 2秒後にキャンセル
try
{
await PerformTaskWithTimeoutAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("処理がタイムアウトしました。");
}
}
static async Task PerformTaskWithTimeoutAsync(CancellationToken token)
{
await Task.Delay(5000, token); // 5秒待機(キャンセル可能)
Console.WriteLine("処理が完了しました。");
}
}
処理がタイムアウトしました。
awaitを使う際の注意点
デッドロックの回避
デッドロックは、非同期処理を使用する際に注意が必要な問題です。
特に、UIアプリケーションでは、await
を使用する際にUIスレッドをブロックしないようにすることが重要です。
ConfigureAwait(false)
を使用することで、元のコンテキストに戻らずに非同期処理を続行することができます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await Task.Run(() => PerformTaskAsync());
}
static async Task PerformTaskAsync()
{
await Task.Delay(1000).ConfigureAwait(false); // UIスレッドをブロックしない
Console.WriteLine("タスクが完了しました。");
}
}
タスクが完了しました。
UIスレッドのブロックを防ぐ
UIアプリケーションでは、長時間実行される処理を非同期で実行することで、UIスレッドがブロックされるのを防ぐことができます。
これにより、ユーザーインターフェースが応答し続け、アプリケーションがスムーズに動作します。
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
class Program : Form
{
private Button button;
public Program()
{
button = new Button { Text = "非同期処理を実行" };
button.Click += async (sender, e) => await PerformLongRunningTaskAsync();
Controls.Add(button);
}
static async Task Main()
{
Application.Run(new Program());
}
private async Task PerformLongRunningTaskAsync()
{
button.Enabled = false; // ボタンを無効化
await Task.Delay(5000); // 5秒待機
MessageBox.Show("非同期処理が完了しました。");
button.Enabled = true; // ボタンを再度有効化
}
}
非同期処理が完了しました。 // メッセージボックスが表示される
非同期メソッドの例外処理
非同期メソッド内で発生した例外は、await
を使用して呼び出す際にキャッチすることができます。
try-catch
ブロックを使用して、例外を適切に処理することが重要です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
try
{
await ThrowExceptionAsync();
}
catch (Exception ex)
{
Console.WriteLine($"例外が発生しました: {ex.Message}");
}
}
static async Task ThrowExceptionAsync()
{
await Task.Delay(1000); // 1秒待機
throw new InvalidOperationException("無効な操作です。");
}
}
例外が発生しました: 無効な操作です。
非同期処理のキャンセルと例外
非同期処理をキャンセルする際には、CancellationToken
を使用します。
キャンセル要求があった場合には、OperationCanceledException
をスローすることができます。
これにより、非同期処理が中断されたことを適切に処理できます。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
var token = cts.Token;
var task = PerformCancellableTaskAsync(token);
// 2秒後にキャンセル
cts.CancelAfter(2000);
try
{
await task; // タスクの完了を待機
}
catch (OperationCanceledException)
{
Console.WriteLine("タスクがキャンセルされました。");
}
}
static async Task PerformCancellableTaskAsync(CancellationToken token)
{
for (int i = 0; i < 5; i++)
{
token.ThrowIfCancellationRequested(); // キャンセル要求をチェック
await Task.Delay(1000); // 1秒待機
Console.WriteLine($"処理中... {i + 1}");
}
}
}
処理中... 1
処理中... 2
タスクがキャンセルされました。
よくある質問
まとめ
この記事では、C#におけるawait
ステートメントの使い方やその動作の仕組み、非同期処理の実践例、応用方法、注意点について詳しく解説しました。
非同期処理を適切に活用することで、アプリケーションのパフォーマンスを向上させ、ユーザー体験を向上させることが可能です。
これを機に、非同期プログラミングの技術を実際のプロジェクトに取り入れて、より効率的なコードを書くことに挑戦してみてください。