[C#] awaitで待機した処理の戻り値を取得する方法を解説
C#でawait
を使用して非同期処理の戻り値を取得するには、Task<T>型
の戻り値を持つメソッドをawait
します。
await
は非同期メソッドの完了を待機し、その結果を取得します。
例えば、Task<int>
を返すメソッドをawait
すると、int型
の結果が得られます。
await
は非同期処理が完了するまで現在のスレッドをブロックせず、処理が完了した時点で結果を返します。
- C#における非同期メソッドの作成方法
- awaitを使用した戻り値の取得方法
- 複数の非同期処理を待機する方法
- エラーハンドリングの実装方法
- 非同期処理の応用例と最適化手法
awaitで戻り値を取得する方法
C#における非同期プログラミングでは、await
キーワードを使用して非同期メソッドの処理が完了するのを待ち、その戻り値を取得することができます。
このセクションでは、Task<T>
を使った非同期メソッドの作成方法や、戻り値の取得方法について詳しく解説します。
Task<T>を使った非同期メソッド
Task<T>
は、非同期メソッドが戻り値を持つ場合に使用される型です。
T
は戻り値の型を示します。
以下は、Task<int>
を返す非同期メソッドの例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task<int> CalculateAsync()
{
// 2秒待機する
await Task.Delay(2000);
return 42; // 計算結果を返す
}
static async Task Main(string[] args)
{
int result = await CalculateAsync(); // 戻り値を取得
Console.WriteLine($"計算結果: {result}");
}
}
計算結果: 42
この例では、CalculateAsyncメソッド
が2秒待機した後に42を返します。
Mainメソッド
では、await
を使ってCalculateAsync
の戻り値を取得しています。
awaitで戻り値を取得する基本的な例
await
を使用することで、非同期メソッドの処理が完了するのを待ち、その戻り値を簡単に取得できます。
以下は、基本的な例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task<string> GetGreetingAsync()
{
await Task.Delay(1000); // 1秒待機
return "こんにちは"; // 挨拶を返す
}
static async Task Main(string[] args)
{
string greeting = await GetGreetingAsync(); // 戻り値を取得
Console.WriteLine(greeting);
}
}
こんにちは
この例では、GetGreetingAsyncメソッド
が1秒待機した後に「こんにちは」を返します。
Mainメソッド
でawait
を使ってその戻り値を取得し、コンソールに出力しています。
戻り値の型に応じた処理
非同期メソッドの戻り値の型に応じて、処理を変えることができます。
例えば、戻り値がint
の場合とstring
の場合で異なる処理を行うことができます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task<int> GetNumberAsync()
{
await Task.Delay(500); // 0.5秒待機
return 10; // 数値を返す
}
static async Task<string> GetTextAsync()
{
await Task.Delay(500); // 0.5秒待機
return "テキスト"; // テキストを返す
}
static async Task Main(string[] args)
{
int number = await GetNumberAsync(); // int型の戻り値を取得
string text = await GetTextAsync(); // string型の戻り値を取得
Console.WriteLine($"数値: {number}, テキスト: {text}");
}
}
数値: 10, テキスト: テキスト
この例では、GetNumberAsyncメソッド
が整数を、GetTextAsyncメソッド
が文字列を返します。
Mainメソッド
でそれぞれの戻り値を取得し、コンソールに出力しています。
voidを返す非同期メソッドの扱い
非同期メソッドは、戻り値がない場合でもasync
キーワードを使って定義できます。
この場合、戻り値の型はTask
になります。
以下は、void
を返す非同期メソッドの例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task PrintMessageAsync()
{
await Task.Delay(1000); // 1秒待機
Console.WriteLine("メッセージを表示しました。");
}
static async Task Main(string[] args)
{
await PrintMessageAsync(); // 非同期メソッドを呼び出す
}
}
メッセージを表示しました。
この例では、PrintMessageAsyncメソッド
が1秒待機した後にメッセージを表示します。
戻り値はないため、Task型
で定義されています。
Mainメソッド
でawait
を使って呼び出しています。
非同期メソッドの作成方法
C#では、非同期メソッドを作成するためにasync
キーワードとTask
またはTask<T>
を使用します。
このセクションでは、非同期メソッドの作成方法や、戻り値の有無に応じたメソッドの定義について詳しく解説します。
asyncキーワードの使い方
async
キーワードは、メソッドが非同期であることを示します。
このキーワードをメソッドの定義に追加することで、await
を使用して非同期処理を待機することができます。
以下は、async
キーワードの使い方の例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await DisplayMessageAsync(); // 非同期メソッドを呼び出す
}
static async Task DisplayMessageAsync()
{
await Task.Delay(1000); // 1秒待機
Console.WriteLine("非同期メッセージを表示しました。");
}
}
非同期メッセージを表示しました。
この例では、DisplayMessageAsyncメソッド
がasync
キーワードを使って定義されており、await
を使って1秒待機した後にメッセージを表示します。
TaskとTask<T>の使い分け
非同期メソッドの戻り値の型によって、Task
またはTask<T>
を使い分けます。
Task
は戻り値がない場合に使用し、Task<T>
は戻り値がある場合に使用します。
戻り値の型 | 使用する型 | 説明 |
---|---|---|
戻り値なし | Task | 処理が完了したことを示す |
戻り値あり | Task<T> | 処理の結果を返す |
以下は、Task
とTask<T>
の使い分けの例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await PrintMessageAsync(); // 戻り値なしの非同期メソッドを呼び出す
int result = await CalculateAsync(); // 戻り値ありの非同期メソッドを呼び出す
Console.WriteLine($"計算結果: {result}");
}
static async Task PrintMessageAsync()
{
await Task.Delay(500); // 0.5秒待機
Console.WriteLine("メッセージを表示しました。");
}
static async Task<int> CalculateAsync()
{
await Task.Delay(500); // 0.5秒待機
return 100; // 計算結果を返す
}
}
メッセージを表示しました。
計算結果: 100
非同期メソッドの例
非同期メソッドは、async
キーワードを使って定義し、await
を使って非同期処理を待機します。
以下は、非同期メソッドの具体的な例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string message = await GetMessageAsync(); // 戻り値を取得
Console.WriteLine(message);
}
static async Task<string> GetMessageAsync()
{
await Task.Delay(2000); // 2秒待機
return "非同期メッセージ"; // メッセージを返す
}
}
非同期メッセージ
この例では、GetMessageAsyncメソッド
が2秒待機した後に「非同期メッセージ」を返します。
Mainメソッド
でその戻り値を取得し、コンソールに出力しています。
戻り値がない場合の非同期メソッド
戻り値がない非同期メソッドは、Task型
で定義します。
以下は、戻り値がない非同期メソッドの例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await PerformActionAsync(); // 戻り値なしの非同期メソッドを呼び出す
}
static async Task PerformActionAsync()
{
await Task.Delay(1500); // 1.5秒待機
Console.WriteLine("アクションを実行しました。");
}
}
アクションを実行しました。
この例では、PerformActionAsyncメソッド
が1.5秒待機した後に「アクションを実行しました。」と表示します。
戻り値はないため、Task型
で定義されています。
Mainメソッド
でawait
を使って呼び出しています。
エラーハンドリング
非同期プログラミングにおいても、エラーハンドリングは重要です。
C#では、try-catch
ブロックを使用して例外を捕捉し、適切に処理することができます。
このセクションでは、非同期メソッドにおけるエラーハンドリングの方法について詳しく解説します。
try-catchを使った例外処理
try-catch
ブロックを使用することで、非同期メソッド内で発生した例外を捕捉し、適切に処理することができます。
以下は、try-catch
を使った例外処理の例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
try
{
await CauseExceptionAsync(); // 例外を引き起こす非同期メソッドを呼び出す
}
catch (Exception ex)
{
Console.WriteLine($"例外が発生しました: {ex.Message}");
}
}
static async Task CauseExceptionAsync()
{
await Task.Delay(1000); // 1秒待機
throw new InvalidOperationException("無効な操作が行われました。"); // 例外をスロー
}
}
例外が発生しました: 無効な操作が行われました。
この例では、CauseExceptionAsyncメソッド
が例外をスローし、Mainメソッド
内のtry-catch
ブロックでその例外を捕捉しています。
非同期メソッド内での例外の扱い
非同期メソッド内で発生した例外は、await
を使用して呼び出したメソッドの呼び出し元に伝播します。
これにより、呼び出し元で例外を捕捉することができます。
以下は、その例です。
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(500); // 0.5秒待機
throw new Exception("エラーが発生しました。"); // 例外をスロー
}
}
例外が発生しました: エラーが発生しました。
この例では、ThrowExceptionAsyncメソッド
が例外をスローし、Mainメソッド
でその例外を捕捉しています。
awaitでの例外処理の注意点
await
を使用する際には、例外が発生する可能性があることを考慮する必要があります。
await
を使ったメソッド呼び出しは、例外が発生した場合にその例外を呼び出し元に伝播します。
したがって、await
を使用する際は、必ずtry-catch
ブロックで囲むことが推奨されます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
try
{
await PerformRiskyOperationAsync(); // リスキーな操作を実行
}
catch (Exception ex)
{
Console.WriteLine($"エラーが発生しました: {ex.Message}");
}
}
static async Task PerformRiskyOperationAsync()
{
await Task.Delay(1000); // 1秒待機
throw new Exception("リスキーな操作中にエラーが発生しました。"); // 例外をスロー
}
}
エラーが発生しました: リスキーな操作中にエラーが発生しました。
Taskの例外とawaitの関係
Task
オブジェクトは、非同期メソッドの実行結果を表します。
非同期メソッドが例外をスローした場合、その例外はTask
オブジェクトに格納されます。
await
を使用すると、Task
の例外が呼び出し元に伝播し、try-catch
ブロックで捕捉することができます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Task task = ThrowExceptionAsync(); // 例外を引き起こす非同期メソッドを呼び出す
try
{
await task; // awaitで例外を捕捉
}
catch (Exception ex)
{
Console.WriteLine($"タスクで例外が発生しました: {ex.Message}");
}
}
static async Task ThrowExceptionAsync()
{
await Task.Delay(500); // 0.5秒待機
throw new Exception("タスク中にエラーが発生しました。"); // 例外をスロー
}
}
タスクで例外が発生しました: タスク中にエラーが発生しました。
この例では、ThrowExceptionAsyncメソッド
が例外をスローし、その例外がTask
オブジェクトに格納されます。
await
を使用してその例外を捕捉し、適切に処理しています。
複数の非同期処理を待機する方法
C#では、複数の非同期処理を同時に実行し、その結果を待機することができます。
これにより、プログラムの効率を向上させることができます。
このセクションでは、Task.WhenAll
やTask.WhenAny
を使った複数の非同期処理の待機方法について解説します。
Task.WhenAllの使い方
Task.WhenAll
は、複数の非同期タスクを同時に実行し、すべてのタスクが完了するのを待つためのメソッドです。
すべてのタスクが成功した場合、結果を取得することもできます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Task task1 = Task1Async(); // 非同期タスク1
Task task2 = Task2Async(); // 非同期タスク2
await Task.WhenAll(task1, task2); // すべてのタスクが完了するのを待つ
Console.WriteLine("すべてのタスクが完了しました。");
}
static async Task Task1Async()
{
await Task.Delay(2000); // 2秒待機
Console.WriteLine("タスク1が完了しました。");
}
static async Task Task2Async()
{
await Task.Delay(1000); // 1秒待機
Console.WriteLine("タスク2が完了しました。");
}
}
タスク2が完了しました。
タスク1が完了しました。
すべてのタスクが完了しました。
この例では、Task1Async
とTask2Async
の2つの非同期タスクを同時に実行し、両方のタスクが完了するのを待っています。
Task.WhenAnyの使い方
Task.WhenAny
は、複数の非同期タスクの中で最初に完了したタスクを待機するためのメソッドです。
最初に完了したタスクの結果を取得することができます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Task task1 = Task1Async(); // 非同期タスク1
Task task2 = Task2Async(); // 非同期タスク2
Task completedTask = await Task.WhenAny(task1, task2); // 最初に完了したタスクを待つ
Console.WriteLine($"完了したタスク: {completedTask.Id}");
}
static async Task Task1Async()
{
await Task.Delay(2000); // 2秒待機
Console.WriteLine("タスク1が完了しました。");
}
static async Task Task2Async()
{
await Task.Delay(1000); // 1秒待機
Console.WriteLine("タスク2が完了しました。");
}
}
タスク2が完了しました。
完了したタスク: 2
タスク1が完了しました。
この例では、Task2Async
が最初に完了し、そのタスクのIDを表示しています。
複数のawaitを使った処理の例
複数のawait
を使って、非同期処理を順番に実行することもできます。
以下は、その例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await Task1Async(); // 最初の非同期タスクを待つ
await Task2Async(); // 次の非同期タスクを待つ
Console.WriteLine("すべてのタスクが完了しました。");
}
static async Task Task1Async()
{
await Task.Delay(2000); // 2秒待機
Console.WriteLine("タスク1が完了しました。");
}
static async Task Task2Async()
{
await Task.Delay(1000); // 1秒待機
Console.WriteLine("タスク2が完了しました。");
}
}
タスク1が完了しました。
タスク2が完了しました。
すべてのタスクが完了しました。
この例では、Task1Async
が完了した後にTask2Async
が実行され、すべてのタスクが完了したことを表示します。
戻り値を持つ複数の非同期処理の結果を取得する方法
戻り値を持つ複数の非同期処理の結果を取得するには、Task.WhenAll
を使用して、すべてのタスクの結果を配列として取得します。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Task<int> task1 = Task1Async(); // 戻り値を持つ非同期タスク1
Task<int> task2 = Task2Async(); // 戻り値を持つ非同期タスク2
int[] results = await Task.WhenAll(task1, task2); // すべてのタスクの結果を取得
Console.WriteLine($"タスク1の結果: {results[0]}, タスク2の結果: {results[1]}");
}
static async Task<int> Task1Async()
{
await Task.Delay(2000); // 2秒待機
return 10; // 結果を返す
}
static async Task<int> Task2Async()
{
await Task.Delay(1000); // 1秒待機
return 20; // 結果を返す
}
}
タスク2の結果: 20, タスク1の結果: 10
この例では、Task1Async
とTask2Async
がそれぞれ整数を返し、Task.WhenAll
を使用して両方の結果を取得しています。
結果は配列として格納され、コンソールに出力されます。
応用例
非同期プログラミングは、さまざまなシナリオで活用できます。
このセクションでは、非同期処理のキャンセル、タイムアウト設定、UIスレッドとの関係、パフォーマンス最適化について解説します。
非同期処理のキャンセル
非同期処理をキャンセルするためには、CancellationToken
を使用します。
これにより、実行中のタスクを安全に中断することができます。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
Task task = LongRunningOperationAsync(cts.Token);
// 1秒後にキャンセルを要求
await Task.Delay(1000);
cts.Cancel();
try
{
await task; // タスクの完了を待つ
}
catch (OperationCanceledException)
{
Console.WriteLine("操作がキャンセルされました。");
}
}
static async Task LongRunningOperationAsync(CancellationToken token)
{
for (int i = 0; i < 5; i++)
{
token.ThrowIfCancellationRequested(); // キャンセル要求をチェック
await Task.Delay(1000); // 1秒待機
Console.WriteLine($"処理中... {i + 1}");
}
}
}
出力結果(キャンセルされた場合):
処理中... 1
操作がキャンセルされました。
この例では、LongRunningOperationAsyncメソッド
が1秒ごとに処理を行い、1秒後にキャンセルが要求されます。
キャンセルが要求されると、OperationCanceledException
がスローされ、適切に処理されます。
非同期処理のタイムアウト設定
非同期処理にタイムアウトを設定することで、指定した時間内に処理が完了しない場合に自動的にキャンセルすることができます。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
using (CancellationTokenSource cts = new CancellationTokenSource())
{
cts.CancelAfter(2000); // 2秒後にキャンセル
try
{
await LongRunningOperationAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("タイムアウトにより操作がキャンセルされました。");
}
}
}
static async Task LongRunningOperationAsync(CancellationToken token)
{
await Task.Delay(5000, token); // 5秒待機(キャンセル可能)
Console.WriteLine("処理が完了しました。");
}
}
タイムアウトにより操作がキャンセルされました。
この例では、LongRunningOperationAsyncメソッド
が5秒待機しますが、2秒後にキャンセルが要求されるため、タイムアウトが発生します。
UIスレッドと非同期処理の関係
UIアプリケーションでは、UIスレッドがブロックされないように非同期処理を使用することが重要です。
非同期メソッドを使用することで、UIが応答し続けることができます。
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
public class MyForm : Form
{
private Button myButton;
public MyForm()
{
myButton = new Button { Text = "処理開始", Dock = DockStyle.Fill };
myButton.Click += async (sender, e) => await StartLongRunningTaskAsync();
Controls.Add(myButton);
}
private async Task StartLongRunningTaskAsync()
{
myButton.Enabled = false; // ボタンを無効化
await Task.Delay(5000); // 5秒待機(非同期処理)
MessageBox.Show("処理が完了しました。");
myButton.Enabled = true; // ボタンを再度有効化
}
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
この例では、ボタンをクリックすると5秒間の非同期処理が開始され、UIがブロックされることなく、処理が完了した後にメッセージボックスが表示されます。
非同期処理のパフォーマンス最適化
非同期処理を最適化するためには、タスクの数や待機時間を適切に管理することが重要です。
以下は、非同期処理のパフォーマンスを向上させるためのポイントです。
- タスクの並列実行: 複数の非同期タスクを同時に実行することで、全体の処理時間を短縮できます。
- 適切な待機時間: 不要な待機時間を減らすことで、処理の効率を向上させます。
- リソースの管理: 非同期処理で使用するリソース(スレッド、メモリなど)を適切に管理し、過剰なリソース消費を避けます。
以下は、複数の非同期タスクを並列に実行する例です。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Task<int> task1 = PerformCalculationAsync(1); // 非同期計算タスク1
Task<int> task2 = PerformCalculationAsync(2); // 非同期計算タスク2
int[] results = await Task.WhenAll(task1, task2); // 並列に実行し、結果を取得
Console.WriteLine($"結果: {results[0]}, {results[1]}");
}
static async Task<int> PerformCalculationAsync(int id)
{
await Task.Delay(3000); // 3秒待機
return id * 10; // 計算結果を返す
}
}
結果: 10, 20
この例では、2つの非同期計算タスクを並列に実行し、全体の処理時間を短縮しています。
非同期処理を適切に活用することで、アプリケーションのパフォーマンスを向上させることができます。
よくある質問
まとめ
この記事では、C#における非同期プログラミングの基本から応用まで、さまざまなトピックを取り上げてきました。
非同期メソッドの作成方法やエラーハンドリング、複数の非同期処理を待機する方法、さらには非同期処理のキャンセルやタイムアウト設定についても詳しく解説しました。
これらの知識を活用することで、より効率的で応答性の高いアプリケーションを開発することが可能になります。
ぜひ、実際のプロジェクトに非同期プログラミングを取り入れ、パフォーマンスの向上を体験してみてください。