戻り値のあるタスクの完了をTask.WhenAll()で待って例外が起きたときに、正常終了したTaskだけでも値を取り出す

もくじ
https://tera1707.com/entry/2022/02/06/144447#Task

やりたいこと

以前の記事で、Taskの中で例外が起きた時のキャッチの仕方を学んだが、今回、Task.WhenAll()で複数の戻り値のあるタスクの完了を待ったときに、そのタスクのどれかで例外があったら、例外があったタスクはなにかデフォルト的な値を返し、正常に終了したタスクは通常の戻り値を返す、ということをしたい。

前提

  • VisualStudio2022 ver 17.2.6
  • .net6

やりかた

Taskを変数で受けておき、WhenAllをawaitしたときになにか例外が起きたら、個別のタスクを受けた変数のExceptionプロパティを見て、例外がないかチェックする。
→例外がなかったらタスクのResultを返し、例外があったらデフォルト値を返す。

using System.Diagnostics;

var t1 = Task<bool>.Run(() => { return true; });
var t2 = Task<string>.Run(() => { throw new  ArgumentOutOfRangeException(); return "デフォ値じゃない"; });
var t3 = Task<int>.Run(() => { return 1; });
var t4 = Task<double>.Run(() => { throw new ArgumentException(); return 1.0; });
var all = Task.WhenAll(t1, t2, t3, t4);

try
{
    await all;
}
catch
{
    var t1Result = GetResultFromTask(t1, false);
    var t2Result = GetResultFromTask(t2, "デフォ値");
    var t3Result = GetResultFromTask(t3, 20);
    var t4Result = GetResultFromTask(t4, 30.0);

    Debug.WriteLine($"t1Result = {t1Result}, t2Result = {t2Result}, t3Result = {t3Result}, t4Result = {t4Result}");
}

// Task実行時に
//  正常終了 → Taskの戻り値を返す
//  例外発生 → 指定した初期値を返す
T GetResultFromTask<T>(Task<T> task, T defaultValue)
{
    if (task.Exception is not null)
    {
        return defaultValue;
    }
    return task.Result;
}

やりかた その2

例外時、自分で決めた「デフォルト値」ではなく、一般的なデフォルト値を返させるときはこうなる。
(とにかく例外時でも例外で落とさずになにか値を返すだけでいい、とかの場合はこちらの方がわかりやすいかも)

using System.Diagnostics;

var t1 = Task.Run(() => { return true; });
var t2 = Task.Run(() => { throw new  ArgumentOutOfRangeException(); return "デフォ値じゃない"; });
var t3 = Task.Run(() => { return 1; });
var t4 = Task.Run(() => { throw new ArgumentException(); return 1.0; });
var all = Task.WhenAll(t1, t2, t3, t4);

try
{
    await all;
}
catch
{
    var t1Result = GetResultFromTask(t1);
    var t2Result = GetResultFromTask(t2);
    var t3Result = GetResultFromTask(t3);
    var t4Result = GetResultFromTask(t4);

    Debug.WriteLine($"t1Result = {t1Result}, t2Result = {t2Result ?? "nullです"}, t3Result = {t3Result}, t4Result = {t4Result}");
}

// Task実行時に
//  正常終了 → Taskの戻り値を返す
//  例外発生 → Tに対応する初期値を返す
T? GetResultFromTask<T>(Task<T> task) 
{
    if (task.Exception is not null)
    {
        return default(T);
    }
    return task.Result;
}

参考

タスクの例外処理

https://blog.xin9le.net/entry/2011/08/19/222300

以前書いた、Taskの中で例外が起きた時のキャッチの仕方の記事

https://qiita.com/tera1707/items/d5a3bc12ffa5f80069a1