funcgetValue(_ value:Int)asyncthrows->Int{if value ==2{throwNSError(domain:"Some Error", code:1)}tryawaitTask.sleep(for:.seconds(value))print(value)return value
}funcsafeFetch()asyncthrows->Int{tryawaitwithThrowingTaskGroup(of:Int.self){ group in
group.addTask {tryawaitgetValue(1)}
group.addTask {tryawaitgetValue(2)}
group.addTask {tryawaitgetValue(3)}var values:[Int]=[]fortryawait value in group {
values.append(value)}return values.reduce(into:0){$0=$0+$1}}}funcdangerFetch()asyncthrows->Int{asynclet value1 =tryawaitgetValue(1)asynclet value2 =tryawaitgetValue(2)asynclet value3 =tryawaitgetValue(3)returntryawait value1 + value2 + value3
}
Discussion
TaskGroup 使ったことないマンだからありがたい記事…!
1つ気になったのが、最初の早見表の「2つ以上(静的)」「1つ以上エラーの可能性がある場合(throws がある場合)」の値が「
ThrowingTaskGroup」になっているけど、「let ... = try await ...またはThrowingTaskGroup」になっていないのは、記事の通りエラーを個別に扱いたくないから?理論上はできるはずだから気になりました!
質問の的を得ているかわかりませんが、
withThrowingTaskGroupはどれかが失敗すると、全てのタスクを実行中でも終了させてくれるけど、async letだとすでに始まってしまったタスクは止められませんでした。なのでどの場面においても有効な手段にはならないはずです(多分)
コードで示すと以下になると思います。
コメントありがとうございます!
おっしゃる通り、早見表の「2つ以上(静的)」「1つ以上エラーの可能性がある場合(throws がある場合)」において、
ThrowingTaskGroupのほかにasync let ... = try await ...(またはasync let ... = await ...)構文を用いることができます。しかし、「
asyncなモノの数」が2つ以上の場合で、かついずれのエラーを無視できない場合、それらを取り扱っている場所からすると、さっさと処理を中断してエラーを投げてあげるのが求められる実装かなと思っています。exec1()とexec2()のそれぞれ return 文の部分に注目します。Swift のタプルは前方から順番に処理が行われます。まず、
exec1()です。こちらはresult3→result4の順に処理されます。num3()の実行から3秒後にエラーが投げられるので、result3は「エラー」となります。この時点でnum4()は実行中ですが「キャンセル」されます。これで return 文のところでサスペンドしているものが無くなり、結果として「
exec1()は3秒経過の後、num3()のエラーを投げる」挙動となります。つづいて
exec2()です。こちらはresult4→result3の順に処理されます。num4()の実行から4秒後にIntが返ってきます。この時点で(すでに4秒経過しているので)num3()はすでにエラーが投げられています。これで return 文のところでサスペンドしているものが無くなったので、結果として「
exec2()は4秒経過の後、num3()のエラーを投げる」挙動となります。exec1()とexec2()は、結果としてどちらも「num3()のエラーを投げる」のですが、それにかかる時間が「3秒」と「4秒」の違いがあります。これを「exec2()は無駄に1秒も時間をかけてしまっている」という問題があると私は考えます。実務等で使用する場面を想像すると、num3()とnum4()のいずれかがエラーだった場合、もうタプルで返すことは不可能なので、もう一方のメソッドの結果は無視してとにかく早くエラーを投げたい と私は思うからです。もう少し表現を変えると、このようになるかなと思います。
num3()とnum4()の片方がエラーだった場合、もう一方の実行はキャンセルした上で、exec2()としてはエラーを投げるこの「もう一方の実行はキャンセルした上で」が
async let ... = try await ...(またはasync let ... = await ...)構文では実現できない場合[1]があります。しかし、
ThrowingTaskGroupを用いる方法ではこの問題が発生しないため[2]、早見表に示した私のおすすめとしてはThrowingTaskGroupのみを掲載させていただいていました。本文中の
このコード例では、num3()・num4() のそれぞれ処理にかかる時間がわかっているので問題にはなりませんが、実際の場合、非同期処理は処理時間が予測できないパターンが多いでしょう。の部分です。 ↩︎ThrowingTaskGroupが適合しているAsyncSequenceを用いて、非同期に結果を受け取るためです。 ↩︎