非同期の戻り値の型 (C#)
非同期メソッドには、次の戻り値の型を指定できます。
- Task操作を実行するが値を返さない非同期メソッドの場合。
- Task<TResult>:値を返す非同期メソッドの場合。
-
void
: イベント ハンドラーの場合。 - アクセス可能な
GetAwaiter
メソッドを持つ任意の型。GetAwaiter
メソッドによって返されるオブジェクトは、System.Runtime.CompilerServices.ICriticalNotifyCompletion インターフェイスを実装する必要があります。 - IAsyncEnumerable<T>: 非同期ストリームを返す非同期メソッドの場合。
非同期メソッドの詳細については、「async および await を使用した非同期プログラミング (C#)を参照してください。
Windows ワークロードに固有のその他の種類も存在します。
- DispatcherOperation。非同期操作の場合は Windows に限定されます。
- IAsyncAction。値を返さないユニバーサル Windows プラットフォーム (UWP) アプリの非同期アクション用です。
- IAsyncActionWithProgress<TProgress>:進行状況を報告するが値を返さない UWP アプリの非同期アクションの場合。
- IAsyncOperation<TResult>。値を返す UWP アプリでの非同期操作用です。
- IAsyncOperationWithProgress<TResult,TProgress>: 進行状況を報告して値を返す UWP アプリの非同期操作用です。
Task の戻り値の型
return
ステートメントを含まない非同期メソッド、またはオペランドを返さない return
ステートメントを含む非同期メソッドには、通常、戻り値の型が Task。 このようなメソッドは、同期的に実行される場合、void
を返します。 非同期メソッドに Task 戻り値の型を使用する場合、呼び出し元のメソッドは、呼び出された非同期メソッドが完了するまで呼び出し元の完了を中断する await
演算子を使用できます。
次の例では、WaitAndApologizeAsync
メソッドに return
ステートメントが含まれていないため、Task オブジェクトが返されます。 Task
を返した場合は、WaitAndApologizeAsync
を待機することが可能です。 Task 型には戻り値がないため、Result
プロパティは含まれません。
public static async Task DisplayCurrentInfoAsync()
{
await WaitAndApologizeAsync();
Console.WriteLine($"Today is {DateTime.Now:D}");
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Console.WriteLine("The current temperature is 76 degrees.");
}
static async Task WaitAndApologizeAsync()
{
await Task.Delay(2000);
Console.WriteLine("Sorry for the delay...\n");
}
// Example output:
// Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.
WaitAndApologizeAsync
を待機するには、void を返す同期メソッドを呼び出す場合と同様に、await 式でなく、await ステートメントを使用します。 この場合、await 演算子を適用しても値は生成されません。 await
の右オペランドが Task<TResult>の場合、await
式は T
の結果を生成します。 await
の右オペランドが Taskの場合、await
とそのオペランドはステートメントです。
次のコードに示すように、WaitAndApologizeAsync
の呼び出しを await 演算子のアプリケーションから分離できます。 ただし、Task
には Result
プロパティがなく、await 演算子が Task
に適用されるときに値が生成されない点に注意してください。
次のコードでは、WaitAndApologizeAsync
メソッドの呼び出しと、メソッドから返されるタスクの待機を分離します。
Task waitAndApologizeTask = WaitAndApologizeAsync();
string output =
$"Today is {DateTime.Now:D}\n" +
$"The current time is {DateTime.Now.TimeOfDay:t}\n" +
"The current temperature is 76 degrees.\n";
await waitAndApologizeTask;
Console.WriteLine(output);
Task<TResult> の戻り値の型
戻り値の型 Task<TResult> は、オペランドが TResult
である return ステートメントを含む非同期メソッドに使用されます。
次の例では、GetLeisureHoursAsync
メソッドに整数を返す return
ステートメントが含まれています。 メソッド宣言では、Task<int>
の戻り値の型を指定する必要があります。 FromResult 非同期メソッドは、DayOfWeekを返す操作のプレースホルダーです。
public static async Task ShowTodaysInfoAsync()
{
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHoursAsync()}";
Console.WriteLine(message);
}
static async Task<int> GetLeisureHoursAsync()
{
DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);
int leisureHours =
today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
? 16 : 5;
return leisureHours;
}
// Example output:
// Today is Wednesday, May 24, 2017
// Today's hours of leisure: 5
ShowTodaysInfo
メソッドの await 式内から GetLeisureHoursAsync
が呼び出されると、await 式は、GetLeisureHours
メソッドによって返されるタスクに格納されている整数値 (leisureHours
の値) を取得します。 await 式の詳細については、「await」を参照してください。
次のコードに示すように、GetLeisureHoursAsync
の呼び出しを await
のアプリケーションから分離することで、await
が Task<T>
から結果を取得する方法について理解を深めることができます。 メソッドの宣言から予想されるように、直ちに待機しない GetLeisureHoursAsync
メソッドの呼び出しは、Task<int>
を返します。 この例では、タスクは getLeisureHoursTask
変数に割り当てられます。 getLeisureHoursTask
は Task<TResult>であるため、TResult
型の Result プロパティが含まれています。 この場合、TResult
は整数型を表します。 await
が getLeisureHoursTask
に適用されると、await 式は getLeisureHoursTask
の Result プロパティの内容に評価されます。 この値は、ret
変数に割り当てられます。
重要
Result プロパティはブロッキング プロパティです。 タスクが完了する前にアクセスしようとすると、タスクが完了して値が使用可能になるまで、現在アクティブなスレッドはブロックされます。 ほとんどの場合、プロパティに直接アクセスするのではなく、await
を使用して値にアクセスする必要があります。
前の例では、Result プロパティの値を取得してメイン スレッドをブロックし、アプリケーションが終了する前に Main
メソッドが message
をコンソールに出力できるようにしました。
var getLeisureHoursTask = GetLeisureHoursAsync();
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await getLeisureHoursTask}";
Console.WriteLine(message);
Void の戻り値の型
void
戻り値の型は、void
戻り値の型を必要とする非同期イベント ハンドラーで使用します。 値を返さないイベント ハンドラー以外のメソッドの場合は、代わりに Task を返す必要があります。これは、void
を返す非同期メソッドを待機できないためです。 このようなメソッドの呼び出し元は、呼び出された非同期メソッドが終了するのを待たずに、完了を続ける必要があります。 呼び出し元は、非同期メソッドによって生成されるすべての値または例外から独立している必要があります。
void を返す非同期メソッドの呼び出し元は、メソッドからスローされた例外をキャッチできません。 このようなハンドルされない例外により、アプリケーションが失敗する可能性があります。 Task または Task<TResult> を返すメソッドが例外をスローした場合、例外は返されたタスクに格納されます。 タスクを待機しているときは、例外が再スローされます。 例外を生成できる非同期メソッドの戻り値の型が Task または Task<TResult> であり、メソッドの呼び出しが待機されていることを確認します。
次の例は、非同期イベント ハンドラーの動作を示しています。 このコード例では、非同期イベント ハンドラーは、完了時にメイン スレッドに通知する必要があります。 その後、メイン スレッドは、非同期イベント ハンドラーが完了するまで待機してから、プログラムを終了できます。
public class NaiveButton
{
public event EventHandler? Clicked;
public void Click()
{
Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
Clicked?.Invoke(this, EventArgs.Empty);
Console.WriteLine("All listeners are notified.");
}
}
public class AsyncVoidExample
{
static readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();
public static async Task MultipleEventHandlersAsync()
{
Task<bool> secondHandlerFinished = s_tcs.Task;
var button = new NaiveButton();
button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;
Console.WriteLine("Before button.Click() is called...");
button.Click();
Console.WriteLine("After button.Click() is called...");
await secondHandlerFinished;
}
private static void OnButtonClicked1(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 1 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 1 is done.");
}
private static async void OnButtonClicked2Async(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 2 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 2 is about to go async...");
await Task.Delay(500);
Console.WriteLine(" Handler 2 is done.");
s_tcs.SetResult(true);
}
private static void OnButtonClicked3(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 3 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 3 is done.");
}
}
// Example output:
//
// Before button.Click() is called...
// Somebody has clicked a button. Let's raise the event...
// Handler 1 is starting...
// Handler 1 is done.
// Handler 2 is starting...
// Handler 2 is about to go async...
// Handler 3 is starting...
// Handler 3 is done.
// All listeners are notified.
// After button.Click() is called...
// Handler 2 is done.
一般化された非同期返戻型とValueTask<TResult>
非同期メソッドでは、"awaiter 型" のインスタンスを返すアクセス可能な GetAwaiter
メソッドがある任意の型を返すことができます。 さらに、GetAwaiter
メソッドから返される型には、System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 属性が必要です。 詳細については、コンパイラ によって読み取られた
この機能は、await
のオペランドの要件を記述する待機可能な式を補完するものです。 一般化された非同期戻り値の型を使用すると、コンパイラは異なる型を返す async
メソッドを生成できます。 一般化された非同期戻り値の型により、.NET ライブラリのパフォーマンスが向上しました。 Task と Task<TResult> は参照型であるため、特に厳密なループで割り当てが発生した場合、パフォーマンスクリティカル パスでのメモリ割り当てがパフォーマンスに悪影響を与える可能性があります。 一般化された戻り値の型のサポートは、メモリ割り当てを増やさないように、参照型ではなく軽量の値型を返すことができることを意味します。
.NET は、一般化されたタスク戻り値の軽量実装として System.Threading.Tasks.ValueTask<TResult> 構造体を提供します。 次の例では、ValueTask<TResult> 構造体を使用して、2 つのサイコロ ロールの値を取得します。
class Program
{
static readonly Random s_rnd = new Random();
static async Task Main() =>
Console.WriteLine($"You rolled {await GetDiceRollAsync()}");
static async ValueTask<int> GetDiceRollAsync()
{
Console.WriteLine("Shaking dice...");
int roll1 = await RollAsync();
int roll2 = await RollAsync();
return roll1 + roll2;
}
static async ValueTask<int> RollAsync()
{
await Task.Delay(500);
int diceRoll = s_rnd.Next(1, 7);
return diceRoll;
}
}
// Example output:
// Shaking dice...
// You rolled 8
一般化された非同期戻り値の型の記述は高度なシナリオであり、特殊な環境での使用を対象としています。 代わりに、Task
、Task<T>
、および ValueTask<T>
型を使用することを検討してください。非同期コードのほとんどのシナリオに対応します。
AsyncMethodBuilder
属性を非同期メソッド (非同期戻り値の型宣言ではなく) に適用して、その型のビルダーをオーバーライドできます。 通常、.NET ランタイムで提供される別のビルダーを使用するには、この属性を適用します。
IAsyncEnumerable<T> を使用する非同期ストリーム
非同期メソッドは、IAsyncEnumerable<T>で表される 非同期ストリームを返す場合があります。 非同期ストリームは、非同期呼び出しが繰り返されるチャンクで要素が生成されたときにストリームから読み取られた項目を列挙する方法を提供します。 次の例は、非同期ストリームを生成する非同期メソッドを示しています。
static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()
{
string data =
@"This is a line of text.
Here is the second line of text.
And there is one more for good measure.
Wait, that was the penultimate line.";
using var readStream = new StringReader(data);
string? line = await readStream.ReadLineAsync();
while (line != null)
{
foreach (string word in line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
{
yield return word;
}
line = await readStream.ReadLineAsync();
}
}
前の例では、文字列から非同期的に行を読み取ります。 各行が読み取られたら、コードは文字列内の各単語を列挙します。 呼び出し元は、await foreach
ステートメントを使用して各単語を列挙します。 メソッドは、ソース文字列から次の行を非同期的に読み取る必要があるときに待機します。
関連項目
- FromResult
- の完了時に非同期タスクを処理する
- async および await を利用した非同期プログラミング (C#)
- async
- 待機
.NET