Shouldn’t I get notified it a inner task fails?
Yes you should:
julia> t = @async begin sleep(1) error("FAIL") end Task (runnable) @0x00007f0fd9203820 julia> fetch(t) ERROR: TaskFailedException: FAIL Stacktrace:  error(::String) at ./error.jl:33  macro expansion at ./REPL:3 [inlined]  (::var"#5#6")() at ./task.jl:333 Stacktrace:  wait at ./task.jl:251 [inlined]  fetch(::Task) at ./task.jl:266  top-level scope at REPL:1
julia> t = Threads.@spawn begin sleep(1) error("FAIL") end Task (runnable) @0x00007f0fd92004f0 julia> fetch(t) ERROR: TaskFailedException: FAIL Stacktrace:  error(::String) at ./error.jl:33  macro expansion at ./REPL:3 [inlined]  (::var"#7#8")() at ./threadingconstructs.jl:113 Stacktrace:  wait at ./task.jl:251 [inlined]  fetch(::Task) at ./task.jl:266  top-level scope at REPL:1
Or are we not speaking of the same thing?
Thank you for the quick anwser!
julia> Threads.@spawn @async error("Fail") Task (done) @0x0000000010821fb0
I did this.
You need to fetch the result.
Shouldn’t this fire anyway?
I think it should. We need structured concurrency for this:
Until we get structured concurrency, making sure to wrap
@spawn always with
@sync is the closest thing you can do.
How would I use
function t() @async error("") return 1 end t()
Either return the task and wait for it or put
@sync syncs the tasks that are lexically enclosed.
Treating something like this safely requires so-called nursery (also known as task group and a few other names). ATM, if you want a task to live longer than the function creating it, you need to manually pass it back to the caller. It’s not safe (you may forget doing so). That’s why I think we need structured concurrency.
I think I don’t mind the unstructured concurrency. But it’s very important to have a stacktrace if something fails! Where is the right place to say that?
In what task should the error be thrown?
I think the whole program should stop running unless you started the task in a try-catch-block.
I think the error should be thrown in the most inner task and propagate out until it reaches a try-catch-block or crashes the program.
The stack trace should go from the occurred error through the started task and so on.
These are my first thoughts
That is structured concurrency.
function f() @async sleep(10) return 1 end try f() |> println catch end dont_have_to_wait_and_do_the_next_thing()
still work with structured concurrency?
You’d write something like
function f(nursery) @async nursery sleep(10) return 1 end try withnursery() do nursery f(nursery) |> println dont_have_to_wait_and_do_the_next_thing() end catch end
I don’t understand the syntax. What is
withnursery() and where is it defined?
The syntax gets more complicated. What’s the point against:
It’s bad because the caller can’t tell if your function or any function that might exist in the call stack starting from your function may leak un-synchronized tasks; i.e., it violates so-called “black box rule”.
For more information, please have a look at Julep https://github.com/JuliaLang/Juleps/blob/master/StructuredConcurrency.md and the discussion in the PR. Your point is related to this discussion on effect system which is effectively a dynamically-scoped nursery: https://github.com/JuliaLang/julia/issues/33248#issuecomment-573365469
I’m not sure, what I want. Both sites have their benefits…
This is my solution as far:
macro my_async(ex) quote @async try $(esc(ex)) catch e println("IN @my_async") # Here I would like to get the definition line number of `ex` @show e end end end
So now I get feedback if a task fails.
FWIW, you can improve the error printing with
macro my_async(ex) quote @async try $(esc(ex)) catch e println("In task:") @error(exception = (e, catch_backtrace())) end end end