Python threading.timer - işlevi her 'n' saniyede bir tekrarlayın


98

Her 0,5 saniyede bir bir işlevi ateşlemek ve zamanlayıcıyı başlatıp durdurup sıfırlayabilmek istiyorum. Python işlemlerinin nasıl çalıştığı konusunda çok bilgim yok ve python zamanlayıcıyla ilgili zorluklar yaşıyorum.

Ancak, iki kez RuntimeError: threads can only be started onceyürüttüğümde almaya devam ediyorum threading.timer.start(). Bunun için bir çalışma var mı? threading.timer.cancel()Her başlamadan önce başvurmayı denedim .

Sözde kod:

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

Yanıtlar:


116

En iyi yol, zamanlayıcı iş parçacığını bir kez başlatmaktır. Zamanlayıcı dizinizin içinde aşağıdakileri kodlarsınız

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

Zamanlayıcıyı başlatan kodda, zamanlayıcıyı setdurdurmak için durdurulan olayı yapabilirsiniz .

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

4
Sonra uykusunu bitirecek ve sonra duracaktır. Python'da bir iş parçacığını zorla askıya almanın bir yolu yoktur . Bu, python geliştiricileri tarafından verilen bir tasarım kararıdır. Ancak net sonuç aynı olacaktır. İplik kısa bir süre çalışmaya devam edecek (uykuda) ancak işlevinizi yerine getirmeyecektir.
Hans Sonra

13
Aslında, zamanlayıcı iş parçacığını hemen durdurabilmek istiyorsanız, sadece threading.Eventve waityerine a kullanın sleep. Ardından, uyandırmak için etkinliği ayarlayın. self.stoppedO zamana bile ihtiyacınız yok çünkü sadece olay bayrağını kontrol ediyorsunuz.
nneonneo

3
Olay, kesinlikle zamanlayıcı iş parçacığını kesmek için kullanılacaktır. Normalde, event.waitsadece zaman aşımına uğrar ve uyku gibi davranır, ancak durdurmak (veya iş parçacığını başka bir şekilde kesmek) istiyorsanız, iş parçacığının olayını ayarlarsınız ve hemen uyanır.
nneonneo

2
Event.wait () kullanmak için cevabımı güncelledim. Öneriler için teşekkürler.
Hans Sonra

1
sadece bir soru, bundan sonra iş parçacığını nasıl yeniden başlatabilirim? arama thread.start()bana verirthreads can only be started once
Motassem MK

33

Gönderen Python setInterval eşdeğeri :

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

Kullanım:

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

Veya burada aynı işlevsellik ama bir dekoratör yerine bağımsız bir işlev olarak :

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

İşte konuları kullanmadan nasıl yapılacağı .


Bir dekoratör kullanırken aralığı nasıl değiştirirsiniz? çalışma zamanında .5'leri 1 saniyeye veya her neyse değiştirmek istediğimi söyle?
lightxx

@lightxx: sadece kullanın @setInterval(1).
jfs

hm. yani ya biraz yavaşım ya da beni yanlış anladın. Çalışma zamanında demek istedim. Kaynak koddaki dekoratörü istediğim zaman değiştirebileceğimi biliyorum. örneğin, her biri bir @setInterval (n) ile dekore edilmiş üç işleve sahiptim. şimdi çalışma zamanında işlev 2'nin aralığını değiştirmek, ancak işlevleri 1 ve 3'ü yalnız bırakmak istiyorum.
lightxx

@lightxx: farklı bir arayüz kullanabilirsiniz, örneğin stop = repeat(every=second, call=your_function); ...; stop().
jfs


31

Zamanlayıcı dizilerini kullanma-

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

bu durdurulabilir t.cancel()


3
Bu kodun cancelyöntemde bir hatası olduğuna inanıyorum . Bu çağrıldığında, iş parçacığı ya 1) çalışmıyor ya da 2) çalışıyor. 1'de) işlevi çalıştırmak için bekliyoruz, bu nedenle iptal iyi çalışacaktır. içinde 2) şu anda çalışıyoruz, bu nedenle iptalin mevcut yürütme üzerinde hiçbir etkisi olmayacaktır. dahası, mevcut yürütme kendini yeniden planlar, böylece gelecekte bir etkisi olmayacak.
Rich Episcopo

1
Bu kod, zamanlayıcı her çalıştığında yeni bir iş parçacığı oluşturur. Kabul edilen cevapla karşılaştırıldığında bu muazzam bir israf.
Adrian W

Yukarıda belirtilen nedenle bu çözümden kaçınılmalıdır: her seferinde yeni bir ileti dizisi oluşturur
Pynchia

22

Hans'ı biraz geliştirmek O zaman cevabını , Timer fonksiyonunu alt sınıflara ayırabiliriz. Aşağıdakiler, tüm "tekrar zamanlayıcı" kodumuz haline gelir ve tüm aynı argümanlarla threading.Timer yerine drop-in yerine kullanılabilir:

from threading import Timer

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

Kullanım örneği:

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

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

aşağıdaki çıktıyı üretir:

foo
foo
foo
foo

ve

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

üretir

bar
bar
bar
bar

Bu yaklaşım zamanlayıcı iş parçacığını başlatmama / iptal etmeme / başlatmama / iptal etmeme izin verecek mi?
Paul Knopf

1
Hayır. Bu yaklaşım, sıradan bir Zamanlayıcı ile yapacağınız her şeyi yapmanıza izin verirken, bunu sıradan bir Zamanlayıcı ile yapamazsınız. Başlangıç ​​/ iptal temel alınan iş parçacığı ile ilişkili olduğundan, daha önce .cancel () 'edilmiş bir iş parçacığını .start () yapmaya çalışırsanız, bir istisna elde edersiniz RuntimeError: threads can only be started once.
right2clicky

1
Gerçekten zarif çözüm! Bunu yapan bir sınıfı dahil etmemiş olmaları garip.
Roger Dahl

bu çözüm çok etkileyici, ancak nasıl tasarlandığını basitçe Python3 threading Timer arayüz belgelerini okuyarak anlamakta zorlandım . Cevap, threading.pymodülün kendisine girerek uygulamayı bilmenin üzerine inşa edilmiş gibi görünüyor .
Adam.at.Epsilon

15

Zamanlayıcıyı OP'nin talep ettiği şekilde kullanarak doğru bir cevap sağlamak amacıyla, swapnil jariwala'nın cevabını geliştireceğim :

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

Küçük bir konsol saati yapmak için swapnil-jariwala kodundaki bazı kodları değiştirdim.

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

ÇIKTI

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

Tkinter Grafik arayüzlü zamanlayıcı

Bu kod saat zamanlayıcısını tkinter ile küçük bir pencereye yerleştirir

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

Bilgi kartı oyunu örneği (tür)

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

çıktı:

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 engelliyorsa, bu sonraki başlama zamanlarına biraz gecikme katmaz mı? Belki satırları değiştirebilirsin, böylece handle_function önce zamanlayıcıyı başlatır ve sonra hFunction'ı çağırır?
Mustache

2

Bunu bir proje için yapmak zorundaydım. Sonunda yaptığım şey, işlev için ayrı bir iş parçacığı başlatmaktı

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

**** kalp atışı benim işlevim, işçi argümanlarımdan biri ****

kalp atışı işlevimin içinde:

def heartbeat(worker):

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

Bu yüzden, iş parçacığını başlattığımda, işlev sürekli olarak 5 saniye bekleyecek, tüm kodumu çalıştıracak ve bunu süresiz olarak yapacak. Süreci öldürmek istiyorsanız, sadece ipliği kapatın.



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

TaskManager()

İşte küçük bir örnek, nasıl çalıştığını daha iyi anlamanıza yardımcı olacak. function taskManager () sonunda kendisine gecikmeli fonksiyon çağrısı yaratır.

"Dalay" değişkenini değiştirmeye çalışın ve farkı görebileceksiniz

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
bunun neden işe yaradığına dair biraz daha bilgi verebilir misiniz?
minocha

Örneğiniz biraz kafa karıştırıcıydı, ilk kod bloğu söylemeniz gereken tek şeydi.
Partack

1

Right2clicky'nin cevabını beğendim, özellikle de bir Thread'ın parçalanmasını ve Timer her tıklandığında yeni bir tane oluşturulmasını gerektirmemesi bakımından. Ek olarak, periyodik olarak çağrılan bir zamanlayıcı geri aramasına sahip bir sınıf oluşturmak kolay bir geçersiz kılmadır. Bu benim normal kullanım durumum:

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

Bu, sınıf yerine işlev kullanan alternatif bir gerçeklemedir. Yukarıdaki @Andrew Wilkins'ten esinlenilmiştir.

Bekleme, uykudan daha doğrudur (işlev çalışma süresini hesaba katar):

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 sınıfıyla başka bir çözüm buldum. Lütfen bana burada hafıza sızıntısı olup olmadığını söyle.

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

Threads kullanarak yukarıdaki harika yanıtlara ek olarak, ana iş parçacığınızı kullanmanız veya zaman uyumsuz bir yaklaşımı tercih etmeniz durumunda - aio_timers Timer sınıfının etrafına kısa bir sınıf sardım (tekrarlamayı etkinleştirmek için)

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

saat başlangıcı: 16024388 40 .9690785

zamanlayıcı_ 1 16024388 45 .980087

zamanlayıcı_ 2 16024388 50 .9806316

zamanlayıcı_ 1 16024388 50 .9808934

zamanlayıcı_ 1 16024388 55 .9863033

zamanlayıcı_ 2 16024388 60 .9868324

zamanlayıcı_ 1 16024388 60 .9876585

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.