これまであまり意識して来ませんでしたが、非同期メソッドにももちろん戻り値は必要です。戻り値には以下のいずれかを指定します。
void型
通常のメソッド同様、非同期メソッドでもreturn文を書かない場合には戻り値にvoidを指定できます。戻り値にvoidを指定するケースは、呼び出し側が非同期メソッドの完了を待機する必要がない場合です。例えばGUIアプリケーションのイベントハンドラで利用する、などが考えられます。
private async void Button_Click(object sender, RoutedEventArgs e) { this.button.IsEnabled = false; await HeavyWork(); //--- 何か重たい処理 this.button.IsEnabled = true; }
Task型
非同期メソッドの戻り値としてreturn文を書かない場合には、void型だけでなくTask型の指定も可能です。この書き方を利用するケースは、例えば以下のように非同期メソッドの完了を待機したい場合です。サンプル中のコメントにもありますが、待機処理中に別の作業を同時に行うなどするとより効率的かと思います。というか、ただ待機するだけなら同期的な通常のメソッドとなんら変わりない (むしろ非同期的にするだけパフォーマンスが劣化する) ので、利用場面をしっかり検討する必要があります。
static void Main() { var task = DoSomethingAsync(); //--- この間、何か別の処理をしたりすると良い task.Wait(); Console.WriteLine("finished!!"); Console.ReadKey(); } static async Task DoSomethingAsync() { await Task.Run(() => Thread.Sleep(3000)); Console.WriteLine("End of DoSomethingAsync"); }
return文がない場合にvoid型とTask型を戻り値として指定できるのは、ちょっと混乱しますね。
Task<T>型
Task<T>型を指定するケースは、非同期メソッドから戻り値を受ける場合です。下記のサンプルのように、return文を使ってT型の値を返すことでTask<T>型を戻り値とすることができます。
static void Main() { var task = CalculateSphereVolumeAsync(1); //--- この間、何か別の処理をしたりすると良い Console.WriteLine("now calculating..."); Console.WriteLine(task.Result); Console.ReadKey(); } static async Task<double> CalculateSphereVolumeAsync(double radius) { var π = await Task.Run(() => { Thread.Sleep(3000); return Math.PI; //--- 頑張って円周率を計算したとする }); return 4 * π * Math.Pow(radius, 3) / 3; }
戻り値の取得はTask型のResultプロパティで行います。タスクの完了待機や戻り値の取得については、TPL入門 (9) - 完了の待機と結果の取得をご参照ください。
戻り値の基本はTask/Task<T>型
非同期メソッドにおいては、return文がない場合はTask型、ある場合にはTask<T>型が戻るのが基本です。しかしGUIアプリケーションで作成する多くのイベントハンドラの戻り値はvoid型で、これにasync/awaitを適用するために、省略形として特別にreturn文がない場合にはvoid型も指定可能としていると考えると良いと思います。また、void型の記述はイベントハンドラへの適用のみに留めるのがベストプラクティスということはMicrosoftのC#/VBの設計担当者であるLucian Wischikさんも仰っています。彼の解説動画が大変参考になるので、一度視聴されることをお勧めします。