Juliaのコルーチン
Juliaには強力なコルーチン機構がある。1つのスレッド内で複数のコルーチンを並行に動作させることで、IOを効率的にハンドルすることができる。しかしコルーチン内で発生したエラーは、エラーが起こった後でタスクを表示したりしないと顕在化しない。
julia> t = @async begin sleep(1); error() end
Task (runnable) @0x000000010bdf6590
タスクに対してwait
すればスタックトレースが出る。
julia> wait(t)
ERROR: TaskFailedException
Stacktrace:
[1] wait(t::Task)
@ Base ./task.jl:352
[2] top-level scope
@ REPL[14]:1
nested task error:
Stacktrace:
[1] error()
@ Base ./error.jl:44
[2] (::var"#15#16")()
@ Main ./REPL[13]:1
しかし、複数の長時間実行されるタスクを並行に実行する場合にはwait
はできない。どちらかに対してwait
するとそこでブロックしてしまうからだ。下の例ではt0
が終了するまでは t1
のエラーが捕捉できない。そもそも t0
が終了するとは限らないので、その場合はt1
のエラーは捕捉できないことになる。
t0 = @async begin sleep(10); error() end
t1 = @async begin sleep(1); error() end
wait(t0)
wait(t1)
@sync
どうしたものか、調べてみたらそのものズバリのissueがあった。
@sync
を使うと、そのブロック内で起動されたタスクすべての終了を同時に待つ事ができる。
@sync begin
t0 = @async begin sleep(10); error() end
t1 = @async begin sleep(1); error() end
end
しかし、どうもすべてが終了するまでエラーが表示されないようで、個別に待つのとあまり変わらない。
ラッパタスクを使う
これを解決する方法も上のissueに書かれている。
対象タスクを、そのタスクをwait
するだけのタスクでラップすればいい。
@async begin
t = @async begin sleep(1); error(); true end
try
wait(t)
catch
display(t)
end
end
さらにすすめて、これをマクロ化したものもあった。
macro monitored_async(expr)
quote
@async begin
t = @async $(esc(expr))
try
wait(t)
catch
display(t)
end
end
end
end
これを@async
の代わりに使えばいい。
julia> @monitored_async begin sleep(1); error(); true end
Task (runnable) @0x000000010cc5e0e0
julia> Task (failed) @0x000000010cc5f210
Stacktrace:
[1] error()
@ Base ./error.jl:44
[2] macro expansion
@ ./REPL[30]:1 [inlined]
[3] (::var"#42#44")()
@ Main ./REPL[28]:4
便利。。@async
をそのまま使うと、すべてのエラーがサイレントに握りつぶされてしまって非常に不便だったので、助かる。