Python threading.timer - lặp lại hàm sau mỗi 'n' giây


94

Tôi muốn tắt một chức năng sau mỗi 0,5 giây và có thể bắt đầu, dừng và đặt lại bộ hẹn giờ. Tôi không quá hiểu về cách hoạt động của các luồng Python và đang gặp khó khăn với bộ đếm thời gian python.

Tuy nhiên, tôi tiếp tục nhận được RuntimeError: threads can only be started oncekhi tôi thực hiện threading.timer.start()hai lần. Có một cách giải quyết cho điều này? Tôi đã thử áp dụng threading.timer.cancel()trước mỗi lần bắt đầu.

Mã giả:

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

Câu trả lời:


112

Cách tốt nhất là bắt đầu chuỗi hẹn giờ một lần. Bên trong chuỗi bộ hẹn giờ của bạn, bạn sẽ viết mã như sau

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

Trong mã bắt đầu bộ hẹn giờ, sau đó bạn có thể thực sethiện sự kiện đã dừng để dừng bộ hẹn giờ.

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

4
Sau đó, nó sẽ kết thúc, nó sẽ ngủ và dừng lại sau đó. Không có cách nào để buộc đình chỉ một sợi trong python. Đây là một quyết định thiết kế được thực hiện bởi các nhà phát triển python. Tuy nhiên, kết quả thực sẽ giống nhau. Luồng của bạn sẽ vẫn chạy (ngủ) trong một thời gian ngắn, nhưng nó sẽ không thực hiện chức năng của bạn.
Hans Then

13
Thực ra, nếu bạn muốn có thể dừng chuỗi hẹn giờ ngay lập tức, chỉ cần sử dụng a threading.Eventwaitthay vì sleep. Sau đó, để đánh thức nó, chỉ cần thiết lập sự kiện. Bạn thậm chí không cần self.stoppedsau đó vì bạn chỉ cần kiểm tra cờ sự kiện.
nneonneo

3
Sự kiện sẽ được sử dụng nghiêm ngặt để làm gián đoạn chuỗi hẹn giờ. Thông thường, chuỗi event.waitsẽ chỉ hết thời gian chờ và hoạt động như một giấc ngủ, nhưng nếu bạn muốn dừng (hoặc làm gián đoạn chuỗi), bạn sẽ đặt sự kiện của chuỗi và nó sẽ thức dậy ngay lập tức.
nneonneo

2
Tôi đã cập nhật câu trả lời của mình để sử dụng event.wait (). Cảm ơn vì những gợi ý.
Hans Then

1
chỉ là một câu hỏi, làm thế nào tôi có thể khởi động lại chuỗi sau đó? gọi điện thread.start()cho tôithreads can only be started once
Motassem MK

33

Từ Tương đương của setInterval trong 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

Sử dụng:

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

Hoặc đây là chức năng tương tự nhưng là chức năng độc lập thay vì trình trang trí :

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

Đây là cách để làm điều đó mà không cần sử dụng chủ đề .


bạn sẽ thay đổi khoảng thời gian như thế nào khi sử dụng decorator? nói rằng tôi muốn thay đổi .5s trong thời gian chạy thành 1 giây hoặc bất cứ điều gì?
lightxx

@lightxx: dùng @setInterval(1)thôi.
jfs

hm. vì vậy hoặc là tôi hơi chậm hoặc bạn hiểu lầm tôi. Ý tôi là lúc chạy. Tôi biết tôi có thể thay đổi trình trang trí trong mã nguồn bất kỳ lúc nào. chẳng hạn, tôi có ba chức năng, mỗi chức năng được trang trí bằng @setInterval (n). bây giờ trong thời gian chạy, tôi muốn thay đổi khoảng thời gian của hàm 2 nhưng để nguyên hàm 1 và 3.
lightxx

@lightxx: bạn có thể sử dụng giao diện khác nhau ví dụ stop = repeat(every=second, call=your_function); ...; stop().
jfs


31

Sử dụng chuỗi hẹn giờ-

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

điều này có thể được dừng lại bởi t.cancel()


3
Tôi tin rằng mã này có một lỗi trong cancelphương pháp. Khi điều này được gọi, luồng là 1) không chạy hoặc 2) đang chạy. Trong 1) chúng tôi đang đợi để chạy chức năng, vì vậy việc hủy bỏ sẽ hoạt động tốt. trong 2) chúng tôi hiện đang chạy, vì vậy việc hủy bỏ sẽ không ảnh hưởng đến việc thực thi hiện tại. hơn nữa, quá trình thực thi hiện tại tự lên lịch lại nên nó sẽ không có hiệu lực trong lần thực hiện trong tương lai.
Rich Episcopo

1
Mã này tạo một chuỗi mới mỗi khi bộ đếm thời gian chạy xuống. Đây là một sự lãng phí lớn so với câu trả lời được chấp nhận.
Adrian W

Nên tránh giải pháp này, vì lý do đã đề cập ở trên: nó tạo ra một chủ đề mới mỗi lần
Pynchia

17

Cải thiện một chút về câu trả lời của Hans Then , chúng ta có thể chỉ cần phân lớp hàm Timer. Phần sau sẽ trở thành toàn bộ mã "bộ đếm thời gian lặp lại" của chúng tôi và nó có thể được sử dụng như một tệp thay thế cho luồng.Timer với tất cả các đối số giống nhau:

from threading import Timer

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

Ví dụ sử dụng:

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

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

tạo ra kết quả sau:

foo
foo
foo
foo

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

sản xuất

bar
bar
bar
bar

Cách tiếp cận này có cho phép tôi bắt đầu / hủy bỏ / bắt đầu / hủy chuỗi hẹn giờ không?
Paul Knopf

1
Không. Mặc dù cách tiếp cận này cho phép bạn làm bất cứ điều gì bạn làm với Bộ hẹn giờ thông thường, nhưng bạn không thể làm điều đó với Bộ hẹn giờ thông thường. Vì bắt đầu / hủy có liên quan đến chuỗi bên dưới, nếu bạn cố gắng .start () một chuỗi trước đó là .cancel () 'ed thì bạn sẽ nhận được một ngoại lệ RuntimeError: threads can only be started once,.
right2clicky

Giải pháp thực sự thanh lịch! Kỳ lạ là họ không chỉ bao gồm một lớp làm điều này.
Roger Dahl

giải pháp này rất ấn tượng, nhưng tôi đã phải vật lộn để hiểu nó được thiết kế như thế nào khi chỉ đọc tài liệu về giao diện Bộ hẹn giờ phân luồng Python3 . Câu trả lời dường như được xây dựng dựa trên việc biết việc triển khai bằng cách đi vào threading.pychính mô-đun.
Adam.at.Epsilon

14

Vì lợi ích của việc cung cấp câu trả lời chính xác bằng cách sử dụng Bộ hẹn giờ như OP yêu cầu, tôi sẽ cải thiện khi trao đổi câu trả lời của 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

Tôi đã thay đổi một số mã trong mã swapnil-jariwala để tạo một đồng hồ bảng điều khiển nhỏ.

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

ĐẦU RA

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

Hẹn giờ với giao diện Đồ họa tkinter

Mã này đặt bộ đếm thời gian đồng hồ trong một cửa sổ nhỏ với 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()

Một ví dụ về trò chơi flashcards (đại loại)

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

đầu ra:

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?
...

nếu hFunction đang chặn, điều này sẽ không gây thêm sự chậm trễ cho thời gian bắt đầu tiếp theo? Có lẽ bạn có thể hoán đổi các dòng để xử lý chức năng bắt đầu hẹn giờ trước và sau đó gọi hFunction?
Ria mép

2

Tôi đã phải làm điều này cho một dự án. Những gì tôi đã làm là bắt đầu một chuỗi riêng cho hàm

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

**** nhịp tim là chức năng của tôi, công nhân là một trong những lý lẽ của tôi ****

bên trong chức năng nhịp tim của tôi:

def heartbeat(worker):

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

Vì vậy, khi tôi bắt đầu chuỗi, hàm sẽ liên tục đợi 5 giây, chạy tất cả mã của tôi và thực hiện điều đó vô thời hạn. Nếu bạn muốn hủy tiến trình, chỉ cần hủy luồng.



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

TaskManager()

Đây là mẫu nhỏ, nó sẽ giúp hiểu rõ hơn về cách nó chạy. function taskManager () ở cuối tự tạo lệnh gọi hàm bị trì hoãn đến nó.

Hãy thử thay đổi biến "dalay" và bạn sẽ có thể thấy sự khác biệt

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
bạn cũng có thể nói thêm một chút về lý do tại sao điều này hoạt động?
minocha

ví dụ của bạn hơi khó hiểu, codeblock đầu tiên là tất cả những gì bạn cần nói.
Partack

1

Tôi thích câu trả lời của right2clicky, đặc biệt là nó không yêu cầu một Chủ đề bị chia nhỏ và một luồng mới được tạo mỗi khi Timer đánh dấu. Ngoài ra, thật dễ dàng ghi đè để tạo một lớp có lệnh gọi lại hẹn giờ được gọi định kỳ. Đó là trường hợp sử dụng bình thường của tôi:

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

Đây là một triển khai thay thế sử dụng hàm thay vì lớp. Lấy cảm hứng từ @Andrew Wilkins ở trên.

Bởi vì chờ đợi chính xác hơn chế độ ngủ (cần tính đến thời gian chạy của hàm):

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

Tôi đã đưa ra một giải pháp khác với lớp SingleTon. Vui lòng cho tôi biết nếu có bất kỳ rò rỉ bộ nhớ nào ở đây.

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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.