Stop/terminate a (sub)task started with @async

Hello, I search a bit here and on the web and found out that obviously there is no “official” way to terminate a started task in julia yet.

However, throw an Interruption error seems to work as a workaround for now.

But now I would like to do it from within a function. Here is the example.
The stating case is working but the stopping case is not.

function switch_timer(switch)
	if switch
	global t = @async while true
			sleep(1)
		end
	else
		@eval :(Base.throwto(t, InterruptException()))
	end
	return 
end

Does someone has a nice idea on how to bring it to work? Thanks

1 Like

Remove the quoting after @eval, (the :( ... ) part).

Right now you just evaluate a value which is an expression:

julia> b = @eval :(Base.throwto(t, InterruptException()))
:(Base.throwto(t, InterruptException()))
1 Like

Ups, sometimes it is the easiest stuff which one oversees. After several hours of trying different solutions for my task I finally made a trivial mistake :wink: Thanks…

EDIT: the answer was to use @async for the throwto.

One more thing here, when I try to call the function through a toggle in a Blink window, the turning on works but turning off does not. It freezes and the REPL becomes irresponsible.

Here is the code

using Blink
using Interact

const run_toggle = toggle(label = "run") |> onchange

const timer = Observable(0.0)

function switch_timer(switch)
	if switch
	global t = @async while true
			sleep(1 / 20)
			timer[] += 1 / 20
		end
	return
	else
		@async Base.throwto(t, InterruptException())
	return
	end
end

on(switch_timer, run_toggle)

widget = vbox(hbox(run_toggle))

window = Window()
body!(window, widget)
1 Like

Yes, that might work sometimes, but you’re messing with the internal state of the scheduler, so it’s not really safe. But you’re just controlling the wrong object: sleep is just a thin wrapper around Timer and the Timer object does support start and stop (actually it maybe just exports the new and close actions right now, but it could export the start and stop names too).

1 Like

Is using schedule(t, InterruptException(), error=true) instead of @async Base.throwto(t, InterruptException()) correct here?

1 Like

In this case using start and stop would be great, thanks… However, I would also like to have a possibility to stop a general task without throwing an InterruptExeption which looks a bit awkward to me here.

No, there’s no generally safe way to interrupt a task. But you can interrupt any waitable object (by closing it).

Unlike Base.throwto, schedule is a public API. If it is not safe to use, could you document this? From its docstring, I think it’s impossible for users (like me) to know that it’s an unsafe API:

https://docs.julialang.org/en/latest/base/parallel/#Base.schedule

That’s true, it lacks a mention that only the owning queue responsible for managing that task should call that method (and calling that method transfers ownership to a new queue)

Thanks. Does it mean that, as a user, it is valid to call schedule(t, exception, error=true) only if the task is not scheduled yet, like this?

julia> t = @task nothing
Task (runnable) @0x00007f3b6dd9cc40

julia> schedule(t, InterruptException(), error=true)
Task (failed) @0x00007f3b6dd9cc40
InterruptException:

Right, if you just made the task, that’s an example of when you own it. You also usually own current_task() (which is good, because wait and yield etc. will implicitly take ownership of it), so you can also de-schedule yourself (e.g. call wait()), or throw exceptions (schedule(error=true); wait(), or, erm, throw()), and implement alternate schedulers (i.e. call wait on something that’s not a Condition object)

When is it useful to use schedule(current_task(), exception, error=true) instead of throw(exception)?

That’s seems unlikely to ever be useful

None of this works anymore? I cannot stop the following task:

using BetterFileWatching
watch_task = @async watch_folder(".") do event
    @info "Something changed!" event
end

sleep(5)

# stop watching the folder
schedule(watch_task, InterruptException(); error=true)

In Julia 1.8

Kind regards

1 Like

This is not a julia 1.8 problem but instead an issue with the package BetterFileWatching.jl.

The culprit is the line:

try wait(watch_task) catch; end

at BetterFileWatching.jl/BetterFileWatching.jl at main · JuliaPluto/BetterFileWatching.jl · GitHub

When you schedule an InterruptException() you silently interrupt the wait of the watch_task async task but not the watch_task task that outlives watch_folder.

This is a simplified version that shows the same issue:

function watch_task()
    while true
        @info "doing something ..."
        sleep(3)
    end
    
end

function watch_folder()
    t = @async watch_task()
    try wait(t) catch; end
end

task = @async watch_folder()

sleep(5)

schedule(task, InterruptException(), error=true)

Eventually consider to open a BetterFileWatchingIssue.jl github issue …

Greetings, Attilio

3 Likes

@attdona it would be better to create a new topic rather than continuing a solved one.