简体   繁体   中英

Calling thread.timer() more than once

The code:

from threading import Timer
import time

def hello():
    print "hello"

a=Timer(3,hello,())
a.start()
time.sleep(4)
a.start()

After running this script I get error: RuntimeError: threads can only be started once so how do I deal with this error. I want to start the timer more than once.

threading.Timer inherits threading.Thread . Thread object is not reusable. You can create Timer instance for each call.

from threading import Timer
import time

class RepeatableTimer(object):
    def __init__(self, interval, function, args=[], kwargs={}):
        self._interval = interval
        self._function = function
        self._args = args
        self._kwargs = kwargs
    def start(self):
        t = Timer(self._interval, self._function, *self._args, **self._kwargs)
        t.start()

def hello():
    print "hello"

a=RepeatableTimer(3,hello,())
a.start()
time.sleep(4)
a.start()

Since I'm used to start my oven timer each time I bake a cookie, I was surprised to see that python's timers are one-shot only. That said I share a small timer class which btw offers some more options on the start method:

  • returns itself to allow a one line timer creation and start
  • optional parameter to restart a new timer or not if timer is still alive

Implementation:

from threading import Timer, Lock


class TimerEx(object):
    """
    A reusable thread safe timer implementation
    """

def __init__(self, interval_sec, function, *args, **kwargs):
    """
    Create a timer object which can be restarted

    :param interval_sec: The timer interval in seconds
    :param function: The user function timer should call once elapsed
    :param args: The user function arguments array (optional)
    :param kwargs: The user function named arguments (optional)
    """
    self._interval_sec = interval_sec
    self._function = function
    self._args = args
    self._kwargs = kwargs
    # Locking is needed since the '_timer' object might be replaced in a different thread
    self._timer_lock = Lock()
    self._timer = None

def start(self, restart_if_alive=True):
    """
    Starts the timer and returns this object [e.g. my_timer = TimerEx(10, my_func).start()]

    :param restart_if_alive: 'True' to start a new timer if current one is still alive
    :return: This timer object (i.e. self)
    """
    with self._timer_lock:
        # Current timer still running
        if self._timer is not None:
            if not restart_if_alive:
                # Keep the current timer
                return self
            # Cancel the current timer
            self._timer.cancel()
        # Create new timer
        self._timer = Timer(self._interval_sec, self.__internal_call)
        self._timer.start()
    # Return this object to allow single line timer start
    return self

def cancel(self):
    """
    Cancels the current timer if alive
    """
    with self._timer_lock:
        if self._timer is not None:
            self._timer.cancel()
            self._timer = None

def is_alive(self):
    """
    :return: True if current timer is alive (i.e not elapsed yet)
    """
    with self._timer_lock:
        if self._timer is not None:
            return self._timer.is_alive()
    return False

def __internal_call(self):
    # Release timer object
    with self._timer_lock:
        self._timer = None
    # Call the user defined function
    self._function(*self._args, **self._kwargs)

Here an example:

from time import sleep

def my_func(msg):
    print(msg)

my_timer = TimerEx(interval_sec=5, function=my_func, msg="Here is my message").start()
sleep(10)
my_timer.start()
sleep(10)

Note: I'm using python 3.7, so I'm not 100% sure this works on Python 2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM