Python threadading.timer - повторюйте функцію кожні 'n' секунд


94

Я хочу вимкнути функцію кожні 0,5 секунди та мати можливість запускати та зупиняти та скидати таймер. Я не надто обізнаний у роботі потоків Python і маю труднощі з таймером python.

Однак я продовжую отримувати, RuntimeError: threads can only be started onceколи виконую threading.timer.start()двічі. Чи є над цим робота? Я намагався подати заявку threading.timer.cancel()перед кожним стартом.

Псевдокод:

t=threading.timer(0.5,function)
while True:
    t.cancel()
    t.start()

Відповіді:


112

Найкращий спосіб - запустити потік таймера один раз. Всередині потоку таймера ви кодуєте наступне

class MyThread(Thread):
    def __init__(self, event):
        Thread.__init__(self)
        self.stopped = event

    def run(self):
        while not self.stopped.wait(0.5):
            print("my thread")
            # call a function

У коді, який запустив таймер, ви можете setзупинити подію, щоб зупинити таймер.

stopFlag = Event()
thread = MyThread(stopFlag)
thread.start()
# this will stop the timer
stopFlag.set()

4
Тоді він закінчить свій сон і згодом зупиниться. Немає можливості примусово призупинити потік у python. Це дизайнерське рішення, прийняте розробниками python. Однак чистий результат буде однаковим. Потік все ще буде працювати (сплячий) ненадовго, але він не буде виконувати вашу функцію.
Ганс Тоді

13
Ну, насправді, якщо ви хочете мати можливість негайно зупинити потік таймера, просто використовуйте a threading.Eventі waitзамість sleep. Потім, щоб пробудити його, просто встановіть подію. self.stoppedТоді вам навіть не потрібно, тому що ви просто перевіряєте прапорець події.
nneonneo

3
Подія буде використовуватися суворо для переривання потоку таймера. Як правило, event.waitпросто час очікування та діятиме як сон, але якщо ви хочете зупинити (або іншим чином перервати потік), ви встановите подію потоку, і вона негайно прокинеться.
nneonneo

2
Я оновив свою відповідь, використовуючи event.wait (). Дякую за пропозиції.
Ганс Тоді

1
просто питання, як я можу перезапустити потік після цього? дзвінок thread.start()дає меніthreads can only be started once
Motassem MK

33

З еквівалента setInterval у python :

import threading

def setInterval(interval):
    def decorator(function):
        def wrapper(*args, **kwargs):
            stopped = threading.Event()

            def loop(): # executed in another thread
                while not stopped.wait(interval): # until stopped
                    function(*args, **kwargs)

            t = threading.Thread(target=loop)
            t.daemon = True # stop if the program exits
            t.start()
            return stopped
        return wrapper
    return decorator

Використання:

@setInterval(.5)
def function():
    "..."

stop = function() # start timer, the first call is in .5 seconds
stop.set() # stop the loop
stop = function() # start new timer
# ...
stop.set() 

Або ось та сама функціональність, але як автономна функція замість декоратора :

cancel_future_calls = call_repeatedly(60, print, "Hello, World")
# ...
cancel_future_calls() 

Ось як це зробити без використання ниток .


як би ви змінили інтервал при використанні декоратора? сказати, що я хочу змінити .5s під час виконання на 1 секунду чи що?
lightxx

@lightxx: просто використовуй @setInterval(1).
jfs

хм так що або я трохи повільний, або ти неправильно мене зрозумів. Я мав на увазі під час виконання. Я знаю, що можу змінити декоратор у вихідному коді в будь-який час. що, наприклад, у мене було три функції, кожна прикрашена @setInterval (n). тепер під час виконання я хочу змінити інтервал функції 2, але залишити функції 1 і 3 в спокої.
lightxx

@lightxx: ви можете використовувати інший інтерфейс , наприклад, stop = repeat(every=second, call=your_function); ...; stop().
jfs


31

Використання потоків таймера

from threading import Timer,Thread,Event


class perpetualTimer():

   def __init__(self,t,hFunction):
      self.t=t
      self.hFunction = hFunction
      self.thread = Timer(self.t,self.handle_function)

   def handle_function(self):
      self.hFunction()
      self.thread = Timer(self.t,self.handle_function)
      self.thread.start()

   def start(self):
      self.thread.start()

   def cancel(self):
      self.thread.cancel()

def printer():
    print 'ipsem lorem'

t = perpetualTimer(5,printer)
t.start()

це можна зупинити t.cancel()


3
Я вважаю, що цей код має помилку в cancelметоді. Коли це викликано, потік або 1) не запущений, або 2) запущений. У 1) ми чекаємо запуску функції, тому скасування буде працювати нормально. у 2) зараз ми працюємо, тому скасування не вплине на поточне виконання. крім того, поточне виконання переплановане, тому це не матиме ефекту в майбутньому.
Rich Episcopo

1
Цей код створює новий потік кожного разу, коли таймер працює. Це колосальне марнотратство порівняно з прийнятою відповіддю.
Adrian W

Цього рішення слід уникати із зазначеної вище причини: воно кожного разу створює нову тему
Pynchia

17

Трохи вдосконаливши відповідь Ганса Тоді , ми можемо просто підкласувати функцію Таймер. Далі стає цілим кодом нашого "таймера повторення", і його можна використовувати як замінник для потокової роботи. Таймер із однаковими аргументами:

from threading import Timer

class RepeatTimer(Timer):
    def run(self):
        while not self.finished.wait(self.interval):
            self.function(*self.args, **self.kwargs)

Приклад використання:

def dummyfn(msg="foo"):
    print(msg)

timer = RepeatTimer(1, dummyfn)
timer.start()
time.sleep(5)
timer.cancel()

видає такий результат:

foo
foo
foo
foo

і

timer = RepeatTimer(1, dummyfn, args=("bar",))
timer.start()
time.sleep(5)
timer.cancel()

виробляє

bar
bar
bar
bar

Чи дозволить такий підхід мені запустити / скасувати / запустити / скасувати потік таймера?
Пол Нопф,

1
Ні. Хоча такий підхід дозволяє робити все, що ви могли б робити зі звичайним таймером, ви не можете робити цього за допомогою звичайного таймера. З початку / скасування пов'язана з основною ниткою, якщо ви намагаєтеся .start () потік , який раніше був .cancel () 'ед , то ви отримаєте виняток, RuntimeError: threads can only be started once.
right2clicky

Дійсно елегантне рішення! Дивно, що вони не просто включили клас, який цим займається.
Роджер Dahl

це рішення дуже вражає, але я намагався зрозуміти, як його було розроблено, просто читаючи документацію інтерфейсу таймера потоків Python3 . Відповідь, мабуть, базується на знанні реалізації шляхом переходу до самого threading.pyмодуля.
Adam.at.Epsilon

14

В інтересах надання правильної відповіді з використанням таймера, як вимагав ОП, я покращу відповідь swapnil jariwala :

from threading import Timer


class InfiniteTimer():
    """A Timer class that does not stop, unless you want it to."""

    def __init__(self, seconds, target):
        self._should_continue = False
        self.is_running = False
        self.seconds = seconds
        self.target = target
        self.thread = None

    def _handle_target(self):
        self.is_running = True
        self.target()
        self.is_running = False
        self._start_timer()

    def _start_timer(self):
        if self._should_continue: # Code could have been running when cancel was called.
            self.thread = Timer(self.seconds, self._handle_target)
            self.thread.start()

    def start(self):
        if not self._should_continue and not self.is_running:
            self._should_continue = True
            self._start_timer()
        else:
            print("Timer already started or running, please wait if you're restarting.")

    def cancel(self):
        if self.thread is not None:
            self._should_continue = False # Just in case thread is running and cancel fails.
            self.thread.cancel()
        else:
            print("Timer never started or failed to initialize.")


def tick():
    print('ipsem lorem')

# Example Usage
t = InfiniteTimer(0.5, tick)
t.start()

3

Я змінив деякий код у коді swapnil-jariwala, щоб зробити маленький консольний годинник.

from threading import Timer, Thread, Event
from datetime import datetime

class PT():

    def __init__(self, t, hFunction):
        self.t = t
        self.hFunction = hFunction
        self.thread = Timer(self.t, self.handle_function)

    def handle_function(self):
        self.hFunction()
        self.thread = Timer(self.t, self.handle_function)
        self.thread.start()

    def start(self):
        self.thread.start()

def printer():
    tempo = datetime.today()
    h,m,s = tempo.hour, tempo.minute, tempo.second
    print(f"{h}:{m}:{s}")


t = PT(1, printer)
t.start()

ВИХІД

>>> 11:39:11
11:39:12
11:39:13
11:39:14
11:39:15
11:39:16
...

Таймер з графічним інтерфейсом tkinter

Цей код розміщує таймер годинника у маленькому вікні за допомогою tkinter

from threading import Timer, Thread, Event
from datetime import datetime
import tkinter as tk

app = tk.Tk()
lab = tk.Label(app, text="Timer will start in a sec")
lab.pack()


class perpetualTimer():

    def __init__(self, t, hFunction):
        self.t = t
        self.hFunction = hFunction
        self.thread = Timer(self.t, self.handle_function)

    def handle_function(self):
        self.hFunction()
        self.thread = Timer(self.t, self.handle_function)
        self.thread.start()

    def start(self):
        self.thread.start()

    def cancel(self):
        self.thread.cancel()


def printer():
    tempo = datetime.today()
    clock = "{}:{}:{}".format(tempo.hour, tempo.minute, tempo.second)
    try:
        lab['text'] = clock
    except RuntimeError:
        exit()


t = perpetualTimer(1, printer)
t.start()
app.mainloop()

Приклад гри на картки (на кшталт)

from threading import Timer, Thread, Event
from datetime import datetime


class perpetualTimer():

    def __init__(self, t, hFunction):
        self.t = t
        self.hFunction = hFunction
        self.thread = Timer(self.t, self.handle_function)

    def handle_function(self):
        self.hFunction()
        self.thread = Timer(self.t, self.handle_function)
        self.thread.start()

    def start(self):
        self.thread.start()

    def cancel(self):
        self.thread.cancel()


x = datetime.today()
start = x.second


def printer():
    global questions, counter, start
    x = datetime.today()
    tempo = x.second
    if tempo - 3 > start:
        show_ans()
    #print("\n{}:{}:{}".format(tempo.hour, tempo.minute, tempo.second), end="")
    print()
    print("-" + questions[counter])
    counter += 1
    if counter == len(answers):
        counter = 0


def show_ans():
    global answers, c2
    print("It is {}".format(answers[c2]))
    c2 += 1
    if c2 == len(answers):
        c2 = 0


questions = ["What is the capital of Italy?",
             "What is the capital of France?",
             "What is the capital of England?",
             "What is the capital of Spain?"]

answers = "Rome", "Paris", "London", "Madrid"

counter = 0
c2 = 0
print("Get ready to answer")
t = perpetualTimer(3, printer)
t.start()

вихід:

Get ready to answer
>>> 
-What is the capital of Italy?
It is Rome

-What is the capital of France?
It is Paris

-What is the capital of England?
...

якщо hFunction блокує, чи не додасть це деякої затримки до наступного часу запуску? Можливо, ви могли б поміняти місцями рядки навколо так, щоб handle_function спочатку запускав таймер, а потім викликав hFunction?
Вуса

2

Мені довелося зробити це для проекту. У підсумку я зробив окремий потік для функції

t = threading.Thread(target =heartbeat, args=(worker,))
t.start()

**** серцебиття - це моя функція, працівник - один з моїх аргументів ****

всередині мого серцебиття:

def heartbeat(worker):

    while True:
        time.sleep(5)
        #all of my code

Отже, коли я запускаю потік, функція буде неодноразово чекати 5 секунд, запускати весь мій код і робити це необмежено довго. Якщо ви хочете вбити процес, просто вбийте нитку.


1

Я реалізував клас, який працює як таймер.

Я залишаю посилання тут на випадок, якщо комусь це потрібно: https://github.com/ivanhalencp/python/tree/master/xTimer


3
Хоча це теоретично може дати відповідь на питання, переважно було б включити сюди основні частини відповіді та надати посилання для довідки.
Карл Ріхтер,

1
from threading import Timer
def TaskManager():
    #do stuff
    t = Timer( 1, TaskManager )
    t.start()

TaskManager()

Ось невеликий зразок, це допоможе краще зрозуміти, як це працює. функція taskManager () в кінці створює відкладений виклик функції до себе.

Спробуйте змінити змінну "dalay", і ви побачите різницю

from threading import Timer, _sleep

# ------------------------------------------
DATA = []
dalay = 0.25 # sec
counter = 0
allow_run = True
FIFO = True

def taskManager():

    global counter, DATA, delay, allow_run
    counter += 1

    if len(DATA) > 0:
        if FIFO:
            print("["+str(counter)+"] new data: ["+str(DATA.pop(0))+"]")
        else:
            print("["+str(counter)+"] new data: ["+str(DATA.pop())+"]")

    else:
        print("["+str(counter)+"] no data")

    if allow_run:
        #delayed method/function call to it self
        t = Timer( dalay, taskManager )
        t.start()

    else:
        print(" END task-manager: disabled")

# ------------------------------------------
def main():

    DATA.append("data from main(): 0")
    _sleep(2)
    DATA.append("data from main(): 1")
    _sleep(2)


# ------------------------------------------
print(" START task-manager:")
taskManager()

_sleep(2)
DATA.append("first data")

_sleep(2)
DATA.append("second data")

print(" START main():")
main()
print(" END main():")

_sleep(2)
DATA.append("last data")

allow_run = False

1
Ви можете також розповісти трохи більше, чому це працює?
minocha

ваш приклад був трохи заплутаним, перший блок блоку - це все, що вам потрібно було сказати.
Partack

1

Мені подобається відповідь right2clicky, особливо тим, що він не вимагає зриву нитки та створення нової кожного разу, коли таймер вистачає. Крім того, легко перевизначити створення класу із зворотним викликом таймера, який періодично викликається. Це мій звичайний варіант використання:

class MyClass(RepeatTimer):
    def __init__(self, period):
        super().__init__(period, self.on_timer)

    def on_timer(self):
        print("Tick")


if __name__ == "__main__":
    mc = MyClass(1)
    mc.start()
    time.sleep(5)
    mc.cancel()

1

Це альтернативна реалізація з використанням функції замість класу. Натхненний @Andrew Wilkins вище.

Оскільки очікування є більш точним, ніж сон (враховується час роботи функції):

import threading

PING_ON = threading.Event()

def ping():
  while not PING_ON.wait(1):
    print("my thread %s" % str(threading.current_thread().ident))

t = threading.Thread(target=ping)
t.start()

sleep(5)
PING_ON.set()

1

Я придумав ще одне рішення для класу SingleTon. Скажіть, будь-ласка, чи є тут витік пам'яті.

import time,threading

class Singleton:
  __instance = None
  sleepTime = 1
  executeThread = False

  def __init__(self):
     if Singleton.__instance != None:
        raise Exception("This class is a singleton!")
     else:
        Singleton.__instance = self

  @staticmethod
  def getInstance():
     if Singleton.__instance == None:
        Singleton()
     return Singleton.__instance


  def startThread(self):
     self.executeThread = True
     self.threadNew = threading.Thread(target=self.foo_target)
     self.threadNew.start()
     print('doing other things...')


  def stopThread(self):
     print("Killing Thread ")
     self.executeThread = False
     self.threadNew.join()
     print(self.threadNew)


  def foo(self):
     print("Hello in " + str(self.sleepTime) + " seconds")


  def foo_target(self):
     while self.executeThread:
        self.foo()
        print(self.threadNew)
        time.sleep(self.sleepTime)

        if not self.executeThread:
           break


sClass = Singleton()
sClass.startThread()
time.sleep(5)
sClass.getInstance().stopThread()

sClass.getInstance().sleepTime = 2
sClass.startThread()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.