[Error] Calling function of a dict from in a Task

I want to call all function that I put in a dict. So far that works great.
But as soon as I add another function the task fails.

julia> d = Dict()
Dict{Any,Any} with 0 entries

julia> d[1] = (()->"1", ())
(var"#21#22"(), ())

julia> t = @async while true
   for (k, (f, args)) in d
      println(f(args...))
   end
   sleep(2)
end

Task (runnable) @0x0000000013a42850

1
1
1
julia> d[2] = (()->"2", ())
(var"#25#26"(), ())
julia> t
Task (failed) @0x0000000013a42850
MethodError: no method matching (::var"#25#26")()
The applicable method may be too new: running in world age 27216, while current world is 27217.
Closest candidates are:
  #25() at none:1 (method too new to be called from this world context.)
macro expansion at .\none:5 [inlined]
(::var"#23#24")() at .\task.jl:358

My bet it’s because you are using the function as a key in a Dict, which is odd enough. Why not use a vector, or do you want to keep the same function from being added twice?

    d = []
    push!(d, (() -> "1", ()) )
    push!(d, (() -> "2", ()) )

    d[1][1](d[1][2]...)
    d[2][1](d[2][2]...)

I’m using an integer as key. The value is a function-args tuple.
I need the key as ID.

Sorry, you are correct, my bad. However this works:

d = Dict()
d[1] = (()->"1", ())
d[2] = (()->"2", ())

t = @async while true
   for (k, (f, args)) in d
      println(f(args...))
   end
   sleep(2)
end

So it isn’t an issue of calling multiple functions from a dict, it’s adding an anonymous function to the Dict after the async task is started. This works however:

function one()
   "1"
end
function two()
   "2"
end

d = Dict()
d[1] = (one, ())

t = @async while true
   for (k, (f, args)) in d
      println(f(args...))
   end
   sleep(2)
end

sleep(5)

d[2] = (two, ())

sleep(3)
t

So according to the error The applicable method may be too new: running in world age 27216, while current world is 27217. The async task is running when the “world age” was 27216, but you passed in a function that was declared when the “world age” was 27217 so the async task can’t find that function since it’s running in an environment that is a step behind.

Yeah that’s the same error I’m getting as well.

But is there a way to do this anyway?

You can use

julia> t = @async while true
   for (k, (f, args)) in d
      println(Base.invokelatest(f, args...))
   end
   sleep(2)
end

You can read Methods · The Julia Language and google around for “julia world age” to find out more.

2 Likes

Stop the task before adding something to the Dict? Maybe something like:

mutable struct BackgroundTask
    todo::Dict{Int, Tuple}
    stop::Ref{Bool}
    task::Union{Task, Nothing}
    BackgroundTask() = new(Dict{Int, Tuple}(), Ref{Bool}(false), nothing)
end

function queue(func, b::BackgroundTask, key, args)
    if b.task != nothing
        b.stop[] = true
        wait(b.task)
    end

    b.stop[] = false
    b.todo[key] = (func, args)

    b.task = @async while b.stop[] == false
        for (k, (f, args)) in b.todo
            println(f(args...))
        end
        sleep(2)
    end
end

b = BackgroundTask()
queue(b, 1, ()) do
    "1"
end

sleep(5)

queue(b, 2, ()) do
    "2"
end

sleep(5)

The calls to queue() could be delayed waiting for the task to finish…you could work around that by changing queue to:

function queue(func, b::BackgroundTask, key, args)
    @async begin
        if b.task != nothing
            b.stop[] = true
            wait(b.task)
        end

        b.stop[] = false
        b.todo[key] = (func, args)

        b.task = @async while b.stop[] == false
            for (k, (f, args)) in b.todo
                println(f(args...))
            end
            sleep(2)
        end
    end
end

So that when the background task finishes a loop, the Dict is updated and the task restarted.

1 Like