Python threading.timer - ทำซ้ำฟังก์ชันทุก ๆ 'n' วินาที


96

ฉันต้องการปิดฟังก์ชันทุกๆ 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()

คำตอบ:


114

วิธีที่ดีที่สุดคือเริ่มเธรดจับเวลาหนึ่งครั้ง ภายในเธรดตัวจับเวลาของคุณคุณจะต้องเขียนโค้ดต่อไปนี้

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 อย่างไรก็ตามผลลัพธ์สุทธิจะเหมือนกัน เธรดของคุณจะยังคงทำงาน (สลีป) ชั่วขณะหนึ่ง แต่เธรดจะไม่ทำงานของคุณ
Hans Then

13
ดีจริงถ้าคุณต้องการที่จะสามารถที่จะหยุดด้ายจับเวลาทันทีเพียงแค่ใช้threading.Eventและแทนwait sleepจากนั้นหากต้องการปลุกให้ตั้งค่าเหตุการณ์ คุณไม่จำเป็นต้องใช้ตอนself.stoppedนั้นเพราะคุณเพิ่งตรวจสอบค่าสถานะเหตุการณ์
nneonneo

3
เหตุการณ์จะถูกใช้อย่างเคร่งครัดเพื่อขัดจังหวะเธรดตัวจับเวลา โดยปกติแล้วการevent.waitหมดเวลาจะทำเหมือนการนอนหลับ แต่ถ้าคุณต้องการหยุด (หรือขัดจังหวะเธรด) คุณจะต้องตั้งค่าเหตุการณ์ของเธรดและมันจะตื่นขึ้นทันที
nneonneo

2
ฉันได้อัปเดตคำตอบเพื่อใช้ event.wait () ขอบคุณสำหรับคำแนะนำ
Hans Then

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() 

นี่คือวิธีการที่จะทำมันได้โดยไม่ต้องใช้หัวข้อ


คุณจะเปลี่ยนช่วงเวลาอย่างไรเมื่อใช้มัณฑนากร? บอกว่าฉันต้องการเปลี่ยน. 5 ที่รันไทม์เป็น 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

19

การปรับปรุงคำตอบของ Hans Thenเล็กน้อยเราสามารถย่อยฟังก์ชัน Timer ได้ ต่อไปนี้จะกลายเป็นโค้ด "ตัวจับเวลาซ้ำ" ทั้งหมดของเราและสามารถใช้แทนดรอปอินสำหรับเธรดได้ตัวจับเวลาที่มีอาร์กิวเมนต์เดียวกันทั้งหมด:

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

วิธีนี้จะช่วยให้ฉันเริ่ม / ยกเลิก / เริ่ม / ยกเลิกเธรดตัวจับเวลาได้หรือไม่
Paul Knopf

1
ไม่ได้แม้ว่าวิธีนี้จะช่วยให้คุณทำอะไรก็ได้ตามที่คุณต้องการด้วยตัวตั้งเวลาธรรมดา แต่คุณไม่สามารถทำได้ด้วยตัวตั้งเวลาธรรมดา เนื่องจาก start / ยกเลิกเกี่ยวข้องกับเธรดที่อยู่ข้างใต้หากคุณพยายามที่จะ. start () เธรดที่เคยเป็น .cancel () 'ed คุณจะได้รับข้อยกเว้น, RuntimeError: threads can only be started once.
right2clicky

1
ทางออกที่สวยหรูจริงๆ! แปลกที่พวกเขาไม่ได้รวมแค่คลาสที่ทำสิ่งนี้
Roger Dahl

การแก้ปัญหานี้เป็นที่น่าประทับใจมาก แต่ผมพยายามที่จะเข้าใจว่ามันได้รับการออกแบบจากเพียงแค่การอ่านเกลียวเอกสารอินเตอร์เฟซจับเวลา Python3 คำตอบดูเหมือนจะต่อยอดจากการรู้การนำไปใช้งานโดยเข้าไปในthreading.pyโมดูลนั้นเอง
Adam.at.Epsilon

15

เพื่อเป็นการให้คำตอบที่ถูกต้องโดยใช้ Timer ตามที่ OP ร้องขอฉันจะปรับปรุงตามคำตอบของ 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 Graphic

รหัสนี้ทำให้ตัวจับเวลานาฬิกาอยู่ในหน้าต่างเล็ก ๆ ด้วย 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()

ตัวอย่างเกม Flashcards (ประเภท)

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
ในขณะที่สิ่งนี้อาจตอบคำถามในทางทฤษฎีแต่ควรรวมส่วนสำคัญของคำตอบไว้ที่นี่และระบุลิงก์สำหรับการอ้างอิง
Karl Richter

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 โดยเฉพาะอย่างยิ่งเมื่อไม่ต้องการให้เธรดถูกฉีกขาดและสร้างเธรดใหม่ทุกครั้งที่ Timer ทำเครื่องหมาย นอกจากนี้ยังเป็นการลบล้างที่ง่ายในการสร้างคลาสด้วยการโทรกลับของตัวจับเวลาที่เรียกเป็นระยะ ๆ นั่นคือกรณีการใช้งานปกติของฉัน:

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()

0

นอกเหนือจากคำตอบที่ยอดเยี่ยมข้างต้นโดยใช้เธรดในกรณีที่คุณต้องใช้เธรดหลักของคุณหรือต้องการวิธีการ async - ฉันได้รวมคลาสสั้น ๆ ไว้รอบคลาสaio_timers Timer (เพื่อเปิดใช้งานการทำซ้ำ)

import asyncio
from aio_timers import Timer

class RepeatingAsyncTimer():
    def __init__(self, interval, cb, *args, **kwargs):
        self.interval = interval
        self.cb = cb
        self.args = args
        self.kwargs = kwargs
        self.aio_timer = None
        self.start_timer()
    
    def start_timer(self):
        self.aio_timer = Timer(delay=self.interval, 
                               callback=self.cb_wrapper, 
                               callback_args=self.args, 
                               callback_kwargs=self.kwargs
                              )
    
    def cb_wrapper(self, *args, **kwargs):
        self.cb(*args, **kwargs)
        self.start_timer()


from time import time
def cb(timer_name):
    print(timer_name, time())

print(f'clock starts at: {time()}')
timer_1 = RepeatingAsyncTimer(interval=5, cb=cb, timer_name='timer_1')
timer_2 = RepeatingAsyncTimer(interval=10, cb=cb, timer_name='timer_2')

นาฬิกาเริ่มต้นที่: 16024388 40 .9690785

timer_ 1 16024388 45 .980087

timer_ 2 16024388 50 .9806316

timer_ 1 16024388 50 .9808934

timer_ 1 16024388 55 .9863033

timer_ 2 16024388 60 .9868324

timer_ 1 16024388 60 .9876585

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.