非同期プログラミングでは戻り値のあるスレッド処理を行うことができます。「C#の Task.Run を使って単純なスレッド処理を行わせる方法」で紹介した単純なスレッド処理では、処理の結果がどのようになったかは知ることができませんでした。
とはいえ戻り値が必要な場合もあるので、C# ではちゃんと戻り値を戻せるスレッド処理が用意されています。この記事では Task.Run() を使用して、呼び出し元に結果を返却(戻り値を戻す)方法のサンプルを紹介していきます。
戻り値のあるスレッド処理とは
例えば A さんに処理をお願いしたはいいものの、Aさんの作業が終わるのを待ってからでないと自分の作業が進められないようなことがあると思います。「完了しました」という報告を受けて作業を再開するためには、やはり仕事がどうなったかの報告が欲しいところですよね。
Task.Run() には戻り値を戻すための処理が組み込まれており、Task クラスのオブジェクトを返却するような仕組みが存在しています。ここではそれらを使ったサンプルをいくつか用意しています。戻り値を知り、それらを使用する方法も解説します。
戻り値のあるスレッド処理のサンプル1
前回に紹介した「単純なスレッド処理」では処理を投げっぱなしになってしまい、依頼元では処理が結果的にどうなったのか知る術はありませんでした。Task.Run() メソッドには、単に処理を実行するだけでなく、戻り値を戻す使い方もあります。今回も .Net のコンソールアプリケーションを作成してサンプルを作成しています。
Console.WriteLine("処理を開始します。");
var task = Task.Run(() =>
{
Console.WriteLine("重い処理を開始します。");
Thread.Sleep(10000);
Console.WriteLine("重い処理が完了しました。");
return 1;
});
Console.WriteLine($"{task.Result}が戻り値でした。");
Console.ReadLine();
上記のサンプルではTask.Run() メソッドで処理を投げた後、Run() メソッドから「return 1;」で呼び出し元に値「1」を返却しています。それ以外の特徴としては Task を生成して処理を実行するときに以下の記述をしていることでしょうか。
var task = Task.Run();
「var task」とすることで、別スレッドに依頼した処理をオブジェクトとして保有できます。この記述方法により、Task.Run() メソッドの戻り値を変数 task で受け取ることができるようになります。実行結果は以下の通りです。
処理を開始します。
重い処理を開始します。
重い処理が完了しました。
1が戻り値でした。
最後の行で「1 が戻り値でした。」とあるように、戻り値が正しく受け取られていることが分かります。このメッセージを出力するときは task.Result と記載しているとおり、Result プロパティにアクセスすることで戻り値を参照することが可能です。
戻り値のあるスレッド処理のサンプル2
Task クラスの Result プロパティにアクセスする場合、戻り値にアクセスするまでは非同期で処理が流れていきますが、戻り値にアクセスする行以降は、task の処理が完了するまでは処理されない「同期的な動き」をします。別のサンプルを確認してみます。
Console.WriteLine("処理を開始します。");
var task = Task.Run(() =>
{
Console.WriteLine("重い処理を開始します。");
Thread.Sleep(10000);
Console.WriteLine("重い処理が完了しました。");
return 1;
});
Console.WriteLine($"処理を依頼しました。");
Console.WriteLine($"戻り値は「{ task.Result }」でした。");
Console.WriteLine($"処理が終了しました。");
Console.ReadLine();
上記のサンプルの実行結果は以下のようになります。
処理を開始します。
処理を依頼しました。
重い処理を開始します。
重い処理が完了しました。
戻り値は「1」でした。
処理が終了しました。
出力結果から分かる通り、「処理を依頼しました。」までは非同期的な動きをしています。また、「処理が終了しました。」のメッセージが「戻り値は「1」でした。」の後に出ていることから「task.Result」で結果にアクセスする行以降は、Task の実行結果を待って処理されていることがわかります。2 本の処理の路線が 1 本になったことが分かりますね。
戻り値のあるスレッド処理のサンプル3
前述のサンプルのように特定の処理が終わるのを待つ、同期的な処理にしたい場合は Wait() メソッドを利用することでも実装可能です。別のサンプルを見てみます。今回は同期的に動くことが分かるように、戻り値を返さない処理にしました。
Console.WriteLine("処理を開始します。");
var task = Task.Run(() =>
{
Console.WriteLine("重い処理を開始します。");
Thread.Sleep(10000);
Console.WriteLine("重い処理を終了します。");
});
Console.WriteLine($"処理を待機させます。");
task.Wait();
Console.WriteLine($"重い処理が完了したので処理を再開します。");
Console.ReadLine();
実行結果は以下のようになります。
処理を開始します。
処理を待機させます。
重い処理を開始します。
重い処理を終了します。
重い処理が完了したので処理を再開します。
結果から分かる通り、task.Wait(); を記述した行以降は、ワーカースレッドで実行している処理が完了してから処理されているのが分かります。Wait メソッドを使用することで、明示的に「処理の完了を待っています」というメッセージ性を持たせることができていますね。
簡単なサンプルでもお分かり頂けると思いますが、非同期処理・同期処理を用いてコーディングをすると、複数本の時間軸を操らなくてはならないため、ややこしさを感じるかと思います。実務レベルの難解な処理に対して非同期処理を持ち込むと、複雑性が増すことは簡単に想像がつきますよね。