Finishing a task

If I’m manipulating tasks with yieldto, without the scheduler, when a task finishes it just hangs the REPL (i.e. yieldto(@task 1+1) hangs). So I have to yieldto back to the main task at the end (or is there some other way, without using the scheduler?)

julia> main = current_task();

julia> t = Task() do
           x = 1 + 1
           yieldto(main, x)
       end
Task (runnable) @0x000000012c776380

julia> yieldto(t)
2

but then the task is still “runnable”, it will hang if it’s yielded to again, and its stack is still allocated (at least, until it’s GC’ed). Is there any way to finish the task? I can manually do t._state = 1, to make it done, then if yielded to it will merely return nothing instead of hanging. Is that how it should be done?

The issue is that the REPL is waiting for a task that hasn’t started yet. @task creates a task but doesn’t run it (you can check with istaskstarted(t)). Running schedule(t) tells the scheduler to start the task. I think you have the right idea. I assumed the task wasn’t getting started, until I tried the following:

t=Task() do
  println(istaskstarted(t))
end

# yieldto(t) # prints true and hangs
schedule(t) # prints true and ends

yieldto is a low-level command, as opposed to schedule, so you might need to manually schedule tasks and set states when using it. The docs seem to discourage its use for those reasons.

What sort of problem are you solving? Do your tasks need to share data or communicate with each other?

1 Like

For context, Simple communication between tasks

What sort of problem are you solving? Do your tasks need to share data or communicate with each other?

Yeah, my problem isn’t “task is waiting on IO so let’s use that time productively”, but rather “coroutines/continuations are cool for decoupling program logic in an AI context”. I don’t think the scheduler is the right tool. yieldto is nice, but it feels like the low-level layer is missing a handful of primitives. Indeed, looking at the scheduler code, it is not “implemented in terms of yieldto”, as one might hope, but rather full of C calls.

You want yield(t), not yieldto(t) if the REPL is supposed to keep running. Or schedule(t); wait(t) if it is not supposed to keep running.

Thank you for the answer. I’m a beginner with tasks!

For me, this works as expected:

julia> t_main = current_task()
Task (runnable) @0x00000001106b0010

julia> t1 = @task begin 
       @show yieldto(t2, 10)
       yieldto(t_main)
       end
Task (runnable) @0x000000013951df90

julia> t2 = @task begin 
       @show yieldto(t1, 100)
       end
Task (runnable) @0x000000013951e0e0

julia> yieldto(t1)
yieldto(t2, 10) = 100

even though of course, I’m on the hook for exception handling etc.

This doesn’t work:

julia> t1 = @task begin 
       @show yield(t2, 10)
       end
Task (runnable) @0x0000000139531900

julia> t2 = @task begin 
       @show yield(t1, 100)
       end
Task (runnable) @0x0000000139531a50

julia> yield(t1)
yield(t2, 10) =     #  ????
julia> 

Presumably it switches to the REPL task in the middle of the @show() call?

With wait, the yield value is absent.

julia> schedule(t1); wait(t1)
yield(t2, 10) = nothing

Sending values back and forth is a big part of my use case. I know that I could work around that in various ways (eg. closing over a Ref, or using Conditions), but at that point, straight yieldto looks equally appealing.

you don’t need to switch all of them