Timed loop

I’m running a loop asynchronously. I’d like to have each iteration take at most max_ns nanoseconds. Here’s what i have so far:

function wait4it(t₀, max_ns)
    t₁ = time_ns()
    t = t₁ - t₀
    if t < max_ns
        s = 1e-9*(max_ns - t)
        sleep(s)
    end
    nothing
end
max_ns = 10^9
@async while true
    t₀ = time_ns()
    sleep(0.1) # and things
    wait4it(t₀, max_ns)
end

But I’m wondering if there is anything similar/better out there?

Maybe this discussion is relevant? Break function on time limit

Thanks! I edited my implementation following some of the ideas there (but by all means not all).

1 Like

We have timedwait in Base (but this polls) or you could follow the following pattern:

cond = Condition()
Timer(x->notify(cond), max_time)
t = @async begin
    # do stuff
    notify(cond)
end
wait(cond)
t.state == :done ? fetch(t) : :timeout

see also: How to wait on a condition for tasks

Better yet to put it in a function! Then you can return a value or a timeout.

2 Likes

I hope I interpreted your code correctly: I assumed you gave an example of what one iteration would look like, i.e. the body of the while/for loop.

I modified your example in the following way:

max_s = 1
cond = Condition()
for i in 1:5
    Timer(_ -> notify(cond), max_s)
    sleep(0.1) # and things
    wait(cond)
    @show i
end

because there is no need to async the stuff that’s done in the loop – I don’t want the loop to continue unless that iteration is done. It worked well, except when the time needed to complete one iteration was longer than max_s, for instance if you replace

sleep(0.1) # and things

with

sleep(2) # and things

it gets stuck on the wait statement and never completes.

No, my code is meant to avoid sleep entirely:

My code has three steps:

  1. setup a condition
  2. startup two tasks: a timer task and a worker task, whichever finishes first notifies the condition.
  3. wait for the condition (in the main process) and return the result or the timeout.

A working version is:

function twait(f::Function, timeout::Real=5)
    cond = Condition()
    Timer(x->notify(cond), timeout)
    t = @async begin
        res = f()
        notify(cond)
        res
    end
    wait(cond)
    t.state == :done ? fetch(t) : :timeout
end

If then we have a “heavy” computation, we can do:

heavy(t) = (sleep(t); return t)  # a heavy computation taking t seconds

julia> twait(2) do
           heavy(1)
       end
1

julia> twait(2) do
           heavy(3)
       end
:timeout

In the second case we get a timeout since 2 < 3.

7 Likes

First and foremost, thank you very much @pbayer, I didn’t know about Timer and I learned something new with the @async call to the “heavy” function that allowed the wait to actually work.

I, of course, completely botched the description of what it was I actually needed… I meant to ask for “…each iteration [to] take at most least max_ns min_ns nanoseconds…”, which lead to my great confusion. But adapting your answer to my needs was simple:

function twait(f, mint)
    cond = Condition()
    Timer(x->notify(cond), mint)
    t = @async $f()
    wait(cond)
    fetch(t)
end

So thanks again!

1 Like

very welcome!

Can I learn why do you interpolate $f() into @async?

I don’t have any good reasons to, f being a function and not a variable. But when I @btime:ed this code I got a small residual (instead of 2 s it took 2.002 s) and I was experimenting to see if interpolating f would make a difference… It didn’t.

In the case where f doesn’t have anything to return (side affects are mutating some other variable in place), replacing the fetch(t) with wait(t) doesn’t change the results above either. In any case, that residual is negligent.