tarabyte
tarabyte

Reputation: 19172

Non-polling/Non-blocking Timer?

The best solution I've found so far is to just use the sleep() function. I'd like to run my own callback function when the event of a timer expiration happens. Is there any event-driven way to go about it?

from time import sleep

# Sleep for a minute
time.sleep(60)

Upvotes: 27

Views: 45256

Answers (4)

mirekphd
mirekphd

Reputation: 6791

Since Python 3.7 (and older versions have reached end of life by now) the asyncio built-in module lets you add a Python sleep() call asynchronously:

import asyncio

async def test():
    print("Hello ... but wait, there is more!")
    await asyncio.sleep(3)
    print("... in the async world!")

Here's some proof that it is non-blocking (courtesy of RealPython):

import asyncio
# Jupyter Notebook users need to allow
# nesting of the asyncio event loop
import nest_asyncio
nest_asyncio.apply()
import time

async def workload(text, duration):
    while duration > 0:
        # run sleep and yield control 
        # back to the event loop (for one cycle)
        await asyncio.sleep(1)
        print(f'{text} counter: sleeping {duration} seconds')
        duration -= 1

async def main():
    # send the workload() coroutine to the background,
    # to let it run concurrently with other tasks, 
    # switching between them at await points
    task_1 = asyncio.create_task(workload('First', 2))
    task_2 = asyncio.create_task(workload('Second', 4))
    task_3 = asyncio.create_task(workload('Third', 8))
    print(f"Started: {time.strftime('%X')}")
    # create await points for each 
    # of the concurrent tasks
    await task_1
    await task_2
    await task_3
    print(f"Ended: {time.strftime('%X')}")

if __name__ == '__main__':
    asyncio.run(main())

Output:

Started: 09:07:21
First counter: sleeping 2 seconds
Second counter: sleeping 4 seconds
Third counter: sleeping 8 seconds
First counter: sleeping 1 seconds
Second counter: sleeping 3 seconds
Third counter: sleeping 7 seconds
Second counter: sleeping 2 seconds
Third counter: sleeping 6 seconds
Second counter: sleeping 1 seconds
Third counter: sleeping 5 seconds
Third counter: sleeping 4 seconds
Third counter: sleeping 3 seconds
Third counter: sleeping 2 seconds
Third counter: sleeping 1 seconds
Ended: 09:07:29

Upvotes: 3

Javier
Javier

Reputation: 1087

I think it could be really simple. Take a look at this example. It works even in a python console!

from threading import Thread
from time import sleep

# Function to be called when the timer expires
def myFunction():
    print 'Did anyone call me?'

# Function with the timer
def myTimer(seconds):
    sleep(seconds)
    myFunction()

# Thread that will sleep in background and call your function
# when the timer expires.
myThread = Thread(target=myTimer, args=(4,))
myThread.start()

Put whatever amount of seconds you want, and keep working with the console or running the main thread/programm. You will notice that the function will be called when the timer comes to an end.

Edit

Another good example, considering the comment from @tarabyte is the one where the function is called only depending on the value of some variable or flag. I hope this would then be the answer @tarabyte is looking for.

from threading import Thread
from time import sleep

myFlag = False

# Function to be called when the flag turns on
def myFunction():
    print 'Did anyone call me?'

def myTimer():
    global myFlag
    while True:
        if myFlag:
            myFunction()
            myFlag = False
        else:
            sleep(1)

# Thread that will sleep in background and call your function
# when the myFlag turns to be True
myThread = Thread(target=myTimer)
myThread.start()

# Then, you can do whatever you want and later change the value of myFlag.
# Take a look at the output inside ipython when the value of myFlag is changed.


In [35]: myFlag
Out[35]: False

In [36]: myFlag = True

In [37]: Did anyone call me?

Upvotes: 13

Aaron Hall
Aaron Hall

Reputation: 395025

Sometimes a simple solution is best, even if it polls the time. I have used this to great success before - it doesn't block if your thread doesn't stop on it.

I think I would manage this most simply by checking times, since this is so much more simple and resource economical than working out a separate threaded solution:

def event_minute_later(event):
    print(time.time()) # use for testing, comment out or delete for production
    return event + 60 < time.time()

And usage:

>>> event = time.time()
>>> print(event)
1393962502.62

>>> event_minute_later(event)
1393962526.73
False
>>> event_minute_later(event)
1393962562.9
True

Upvotes: 9

Bach
Bach

Reputation: 6217

There's a built-in simple solution, using the threading module:

import threading

timer = threading.Timer(60.0, callback)
timer.start()  # after 60 seconds, 'callback' will be called

## (in the meanwhile you can do other stuff...)

You can also pass args and kwargs to your callback. See here.

Upvotes: 36

Related Questions