How to kill thread?

Is there a way to kill a thread spawned by Threads.@spawn macro?

1 Like

Can you perhaps use Base.throwto to throw an exception to the task?

Thank you for your reply. I will give it a go as you suggest.

Actually I needed to be more specific. I am trying to do the following:

Threads.@spawn run(pipeline(`adb shell "logcat -v time | grep onScanResult"`, io))

Initially I discovered that run() was a blocking call and I could’t do anything after that. So I let a separate thread call run().

I am naively expecting that if I could somehow kill the thread, then the process launched by that thread may also be killed…but this is a wrong understanding?

I’m not sure about that, but I know that Base.throwto works well when throwing an exception to a task started with @async. Tasks on other threads might behave differently.

1 Like

You need to get the task handle to kill it, like that:

using .Threads

function hanging()
    println("I'm hanging on thread:", threadid())
    while true
        sleep(1)
    end
end

julia> hang = Threads.@spawn hanging();

julia> I'm hanging on thread:2

julia> hang
Task (runnable) @0x000000010f5698d0

julia> schedule(hang, ErrorException("stop"), error=true)
Task (failed) @0x000000010f5698d0
stop
try_yieldto(::typeof(Base.ensure_rescheduled), ::Base.RefValue{Task}) at ./task.jl:611
....
4 Likes

Thank you for the detailed information! I think now I can leverage your code to put extra code to kill a process inside the thread before it’s death.

According to @jameson, schedule(..., error=true) is not the correct way to terminate the task already started and there is no way to safely interrupt a task. Stop/terminate a (sub)task started with @async - #8 by jameson

First of all, you don’t need to use thread to run I/O concurrently. @async is enough. Also, for running external processes concurrently, you don’t even need to use @async:

julia> proc = run(`sleep 60`; wait = false)
Process(`sleep 60`, ProcessRunning)

julia> kill(proc)

julia> proc
Process(`sleep 60`, ProcessSignaled(15))
5 Likes

killing is never safe, I didn’t suggest otherwise. A safe way would be a task handling its own channel, like the following:

struct Stop end
struct Continue end

function safe_hanging(ch::Channel)
    println("I'm hanging on thread ", threadid())
    signal = Continue()
    while true
        isready(ch) && (signal = take!(ch))
        signal == Stop() && break
        sleep(1)
    end
    println("stopped!")
end

julia> mytask = Ref{Task}();

julia> ch = Channel(safe_hanging, taskref=mytask, spawn=true)
I'm hanging on thread 2
Channel{Any}(sz_max:0,sz_curr:0)

julia> mytask[]
Task (runnable) @0x000000011001f850

julia> put!(ch, Stop());
stopped!

julia> mytask[]
Task (done) @0x000000011001f850

Also a task with its own try catch error handling could be a safe way.

8 Likes

Yes, you need so-called “cancellation token” for safe task cancellation. It would be nice to have a uniform API to do this in Julia. For more discussion see:

https://github.com/JuliaLang/julia/issues/33248

I also created a proof-of-concept that has an API cancel!(::TaskContext): GitHub - tkf/Awaits.jl: [WIP] Structured concurrency for parallel computing

5 Likes

@pbayer, @tkf, @baggepinnen, thank you all for your valuable instructions and concrete code examples!

It took me quite a while until I completely familiarized myself with every details in your answers.

@tkf, It took me days to catch the presence of wait=false in run() call. How silly of me!