Python threading.timer- 'n'초마다 함수 반복


96

0.5 초마다 기능을 실행하고 타이머를 시작 및 중지하고 재설정 할 수 있기를 원합니다. 저는 파이썬 스레드가 어떻게 작동하는지 잘 모르고 파이썬 타이머에 어려움을 겪고 있습니다.

그러나 두 번 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
그런 다음 수면을 마치고 나중에 멈 춥니 다. 파이썬에서 스레드 를 강제로 일시 중단하는 방법은 없습니다 . 이것은 파이썬 개발자가 내린 디자인 결정입니다. 그러나 최종 결과는 동일합니다. 스레드는 잠시 동안 계속 실행 (휴면)되지만 기능을 수행하지 않습니다.
Hans Then

13
즉시 타이머 스레드를 중지 할 수 있도록하려면 음, 사실, 그냥 사용 threading.Event하고 wait대신 sleep. 그런 다음 깨우려면 이벤트를 설정하십시오. self.stopped이벤트 플래그 만 확인하기 때문에 then이 필요하지 않습니다 .
nneonneo

3
이벤트는 타이머 스레드를 인터럽트하는 데 엄격하게 사용됩니다. 일반적으로 event.wait시간 초과가 발생하고 절전 모드처럼 작동하지만 중지 (또는 스레드 중단)를 원할 경우 스레드의 이벤트를 설정하면 즉시 깨어납니다.
nneonneo

2
event.wait ()를 사용하도록 답변을 업데이트했습니다. 제안 해 주셔서 감사합니다.
Hans Then

1
질문 하나, 그 후에 스레드를 어떻게 다시 시작할 수 있습니까? 전화는 thread.start()저를 준다threads can only be started once
Motassem MK에게

33

파이썬의 setInterval과 동등한 것에서 :

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 2013-08-29

흠. 그래서 내가 조금 느리거나 당신이 나를 오해하고 있습니다. 나는 런타임에 의미했다. 언제든 소스 코드에서 데코레이터를 변경할 수 있다는 것을 알고 있습니다. 예를 들어, 각각 @setInterval (n)으로 장식 된 세 가지 함수가 있습니다. 이제 런타임에 기능 2의 간격을 변경하고 기능 1과 3은 그대로 둡니다.
lightxx

@lightxx : 다른 인터페이스를 사용할 수 있습니다 (예 : stop = repeat(every=second, call=your_function); ...; stop().
jfs 2013-08-31

1
@lightxx : 여기가 아닌 데코레이터의 stop = call_repeatedly(interval, 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) 현재 실행 중이므로 취소는 현재 실행에 영향을 미치지 않습니다. 또한 현재 실행은 자체적으로 일정이 변경되어 향후 etiher에 영향을 미치지 않습니다.
Rich Episcopo

1
이 코드는 타이머가 실행될 때마다 새 스레드를 만듭니다. 이것은 받아 들여지는 대답에 비해 엄청난 낭비입니다.
Adrian W

이 솔루션은 위에서 언급 한 이유로 피해야합니다. 매번 새 스레드를 생성합니다
Pynchia

19

Hans Then의 답변 에 대해 조금 개선 하면 Timer 함수를 하위 클래스로 만들 수 있습니다. 다음은 전체 "반복 타이머"코드가되며 모든 동일한 인수를 사용하여 threading.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
이 접근 방식을 사용하면 일반 타이머로 할 수있는 모든 작업을 수행 할 수 있지만 일반 타이머로는 수행 할 수 없습니다. 시작 / 취소는 기본 스레드와 관련되어 있으므로 이전에 .cancel () 처리 된 스레드를 .start ()하려고하면 예외가 발생 RuntimeError: threads can only be started once합니다.
right2clicky apr

1
정말 우아한 솔루션! 이 작업을 수행하는 클래스 만 포함하지 않은 것이 이상합니다.
Roger Dahl

이 솔루션은 매우 인상적이지만 Python3 스레딩 타이머 인터페이스 문서 를 읽는 것만으로도 어떻게 설계되었는지 이해하는 데 어려움을 겪었습니다 . 답은 threading.py모듈 자체 로 이동하여 구현을 아는 것에서 구축되는 것으로 보입니다 .
Adam.at.Epsilon

15

OP가 요청한 Timer를 사용하여 정답을 제공하기 위해 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을 호출하도록 줄을 바꿀 수 있습니까?
Mustache

2

나는 프로젝트를 위해 이것을해야했다. 내가 한 일은 함수에 대해 별도의 스레드를 시작하는 것이 었습니다.

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

**** 하트 비트는 내 기능이고, 작업자는 내 주장 중 하나입니다 ****

내 심장 박동 기능 내부 :

def heartbeat(worker):

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

따라서 스레드를 시작하면 함수는 반복적으로 5 초 동안 대기하고 모든 코드를 실행하고 무기한으로 수행합니다. 프로세스를 종료하려면 스레드를 종료하십시오.



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

TaskManager()

여기에 작은 샘플이 있습니다. 어떻게 실행되는지 이해하는 데 도움이 될 것입니다. 마지막에 taskManager () 함수는 자신에 대한 지연된 함수 호출을 만듭니다.

"달 레이"변수를 변경하면 차이를 볼 수 있습니다.

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에서 영감을 얻었습니다.

wait는 sleep보다 정확하기 때문에 (함수 런타임을 고려합니다) :

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

스레드를 사용하는 위의 훌륭한 답변 외에도 메인 스레드를 사용하거나 비동기 방식을 선호하는 경우 -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 0.980087

timer_ 2 16,024,388 50 0.9806316

timer_ 1 16024388 50 0.9808934

timer_ 1 16024388 55 0.9863033

timer_ 2 16,024,388 60 0.9868324

timer_ 1 16024388 60 0.9876585

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.