Oops I found the solution. It is because julia exists immediately after receiving a SIGINT by default when running a script. We can disable that by Base.exit_on_sigint(false):
running = true
function task()
try
i = 0
while running
println(i+=1)
sleep(1)
end
println("Task is stopped")
catch err
global running = false
@error "Task is interrupted" exception = (err, catch_backtrace())
end
end
Base.exit_on_sigint(false)
@sync begin
@async task()
@async task()
end
println("Program exited")
Now the code will exit gracefully after ctrl+c:
~/julia ./julia async-interupt.jl
1
1
^C┌ Error: Task is interrupted
│ exception =
│ InterruptException:
│ Stacktrace:
│ [1] poptask(W::Base.InvasiveLinkedListSynchronized{Task})
│ @ Base ./task.jl:935
│ [2] wait()
│ @ Base ./task.jl:944
│ [3] wait(c::Base.GenericCondition{Base.Threads.SpinLock})
│ @ Base ./condition.jl:124
│ [4] _trywait(t::Timer)
│ @ Base ./asyncevent.jl:129
│ [5] wait
│ @ ./asyncevent.jl:147 [inlined]
│ [6] sleep(sec::Int64)
│ @ Base ./asyncevent.jl:232
│ [7] task()
│ @ Main ~/julia/async-interupt.jl:7
│ [8] (::var"#2#4")()
│ @ Main ./task.jl:490
└ @ Main ~/julia/async-interupt.jl:11
Task is stopped
Program exited