68

I have to do a program in python that needs to execute for some time and then (does not matter where it was executing) it must dump information to a file, close the file and then exit.

The behavior here is equivalent in JavaScript to using setTimeout(func, 1000000) where its first parameter (func) would be a pointer to the function with the exit code and its second parameter would be the time available to the program to execute.

I know how to make this program in C (using SO signals) but with python

pb2q
  • 58,613
  • 19
  • 146
  • 147
brunoais
  • 6,258
  • 8
  • 39
  • 59

4 Answers4

97

In practice, a Timer is probably the simplest way to do what you want.

This code will do the following:

  • After 1 second, it prints "arg1 arg2"
  • After 2 seconds, it prints "OWLS OWLS OWLS"

===

from threading import Timer

def twoArgs(arg1,arg2):
    print arg1
    print arg2
    print ""

def nArgs(*args):
    for each in args:
        print each

#arguments: 
#how long to wait (in seconds), 
#what function to call, 
#what gets passed in
r = Timer(1.0, twoArgs, ("arg1","arg2"))
s = Timer(2.0, nArgs, ("OWLS","OWLS","OWLS"))

r.start()
s.start()

===

The above code will most likely solve your problem.

But! There is alternative way, that doesn't use multithreading. It works much more like Javascript, which is single-threaded.

For this single-thread version, all you need to do is store the function and its arguments in an object, along with the time at which the function should be run.

Once you have the object containing the function call and the timeout, just periodically check if the function is ready to execute.

The right way to do this is by making a priority queue to store all of the functions we want to run in the future, as shown in the code below.

Just like in Javascript, this approach makes no guarantee that the function will be run exactly on time. A function that takes a very long time to run will delay the functions after it. But it does guarantee that a function will be run no sooner than its timeout.

This code will do the following:

  • After 1 second, it prints "20"
  • After 2 seconds, it prints "132"
  • After 3 seconds, it quits.

===

from datetime import datetime, timedelta
import heapq

# just holds a function, its arguments, and when we want it to execute.
class TimeoutFunction:
    def __init__(self, function, timeout, *args):
        self.function = function
        self.args = args
        self.startTime = datetime.now() + timedelta(0,0,0,timeout) 

    def execute(self):
        self.function(*self.args)

# A "todo" list for all the TimeoutFunctions we want to execute in the future
# They are sorted in the order they should be executed, thanks to heapq
class TodoList: 
    def __init__(self):
        self.todo = []

    def addToList(self, tFunction):
        heapq.heappush(self.todo, (tFunction.startTime, tFunction))

    def executeReadyFunctions(self):
        if len(self.todo) > 0:
            tFunction = heapq.heappop(self.todo)[1]
            while tFunction and datetime.now() > tFunction.startTime:
                #execute all the functions that are ready
                tFunction.execute()
                if len(self.todo) > 0:
                    tFunction = heapq.heappop(self.todo)[1]
                else:
                    tFunction = None                    
            if tFunction:
                #this one's not ready yet, push it back on
                heapq.heappush(self.todo, (tFunction.startTime, tFunction))

def singleArgFunction(x):
    print str(x)

def multiArgFunction(x, y):
    #Demonstration of passing multiple-argument functions
    print str(x*y)

# Make some TimeoutFunction objects
# timeout is in milliseconds
a = TimeoutFunction(singleArgFunction, 1000, 20)
b = TimeoutFunction(multiArgFunction, 2000, *(11,12))
c = TimeoutFunction(quit, 3000, None)

todoList = TodoList()
todoList.addToList(a)
todoList.addToList(b)
todoList.addToList(c)

while True:
    todoList.executeReadyFunctions()

===

In practice, you would likely have more going on in that while loop than just checking if your timeout functions are ready to go. You might be polling for user input, controlling some hardware, reading data, etc.

manimino
  • 1,265
  • 1
  • 11
  • 11
  • does `from threading import Timer` reuse the same thread or use a threadpool? is there a way to pass it a threadpool instance? – Alexander Mills Mar 10 '19 at 08:31
  • It takes FOREVER for the timer to trigger for me at all. Perhaps it's because I'm trying to interrupt a raw_input command after a certain amount of time lol but has anybody else noticed this? – ColorCodin Jul 03 '19 at 13:42
15

You can use signals in python as well (unix only)

import signal, sys

# install a SIGALRM handler 

def handler(signum, frame):
    print "got signal, exiting"
    sys.exit(1)

signal.signal(signal.SIGALRM, handler)

# emit SIGALRM after 5 secs

signal.setitimer(signal.ITIMER_REAL, 5)

# do stuff

i = 1
while True:
    if i % 100000 == 0:
        print i
    i += 1

Docs: http://docs.python.org/library/signal.html

georg
  • 211,518
  • 52
  • 313
  • 390
  • Anything compatible with windows and linux? (I need it compatible with both) – brunoais Apr 14 '12 at 15:48
  • 7
    No idea. Something like http://docs.python.org/library/threading.html#timer-objects maybe. – georg Apr 14 '12 at 16:12
  • 2
    @thg435 I've tested. The above comment should be the accepted answer. – Fábio Santos Jul 11 '12 at 09:14
  • Oh well. As the author of the post and the answer in the comments is the same, I'll just accept the question even though the best answer is definitely in the comments. – brunoais Oct 14 '12 at 07:22
  • 1
    @brunoais: to make this more helpful you might want to write your own answer explaining how exactly that link helped you and accept it to show others that that is the correct solution. Think on people who have the same problem and happen to google this topic out. – georg Oct 14 '12 at 09:42
  • 1
    @thg435 Ok, I'll do that, but for now I'm really super busy. I'll write that answer as I can and then I'll post as soon as it is complete. I have some work secrecy so I will not be able to give all the details – brunoais Oct 14 '12 at 14:18
  • `threading.Timer` is not the same thing, since `setTimeout` is single-threaded. – neuront Dec 29 '12 at 07:53
15

There is a nice solution using asyncio in python 3:

import asyncio

def async_call_later(seconds, callback):
    async def schedule():
        await asyncio.sleep(seconds)

        if asyncio.iscoroutinefunction(callback):
            await callback()
        else:
            callback()

    asyncio.ensure_future(schedule())

async def do_something_async():
    await asyncio.sleep(0.5)
    print('Now! async')

async def main():
    print('Scheduling...')

    async_call_later(3, do_something_async)
    async_call_later(3, lambda: print('Now!'))

    print('Waiting...')

    await asyncio.sleep(4)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Note that the sleep and similar functions in python expect a number of seconds, so I have replicated that. But you can supply fractions if you need milliseconds. (e.g 0.5 => 500ms).

An advantage of this approach over asyncio.call_later is that call_later only works with a synchronous callback. This implementation awaits if the callback is a coroutine so it is slightly more robust.

jaybeeuu
  • 1,013
  • 10
  • 25
  • 1
    i think the asyncio solution is solid, but i wonder if there is a way to do this using a threadpool or something –  Mar 10 '19 at 07:34
8

You could use the call_later method of python3's asyncio event loop. Below example will just work.

import asyncio

loop = asyncio.get_event_loop()

def callback():
    print("callback")
    loop.call_later(1, callback)

loop.call_later(1, callback)

async def main():
    while True:
        await asyncio.sleep(1)

loop.run_until_complete(main())
nurettin
  • 11,090
  • 5
  • 65
  • 85