python從寫循環定時器學習Timer

python 如何寫一個定時器,循環定時作某一操做呢?html

Timer 對象

from threading import Timer
def hello(): 
    print "hello, world" 
   
t = Timer(10.0, hello) 
t.start()

10秒後輸出:python

hello, world

重點研究 t = Timer(10.0, hello) 這句代碼,python 提供了一個Timer 對象,它會在指定的時間後執行某一操做;它的完整形式:多線程

class threading.Timer(interval, function, args=[], kwargs={})

interval 是時間間隔,function 是可調用的對象,argskwargs 會做爲 function 的參數。python2.7

注意:這裏只會執行一次 function,而不會一直定時執行,且 Timer 在執行操做的時候會建立一個新的線程。函數

Timer 在 python2 和 python3 有點區別:線程

# python2.7
def Timer(*args, **kwargs):
    return _Timer(*args, **kwargs)
# python3.7
class Timer(Thread):
    pass

在 python3,TimerThread 的子類;在 python2,_TimerThread 的子類,而 Timer 只是 _Timer 類的工廠方法。code

上面的代碼只會打印一次 hello, world 後退出,那麼如何循環間隔打印呢?htm

粗陋的循環定時器

一種方法是在 function 裏繼續註冊一個 Timer,這樣就能夠在下一個 interval 繼續執行 function對象

from threading import Timer
def hello(): 
    print "hello, world" 
    Timer(10.0, hello) .start()

t = Timer(10.0, hello) 
t.start()

每隔 10 秒輸出一個 hello, world繼承

達到效果了,可是這裏面好像有點問題。回到 Timer 自己,它是一個 thread,每次循環間隔操做,系統都要建立一個線程,而後再回收,這對系統來講開銷很大。若是時間間隔 interval 很短,系統會一會兒建立不少線程,這些線程很難快速回收,致使系統內存和cpu資源被消耗掉。
因此不提倡在 function 裏繼續註冊一個 Timer。

更 pythonic 循環定時器

這裏有更 pythonic 的方法:

from threading import _Timer
def hello():
     print "hello, world"
class RepeatingTimer(_Timer): 
    def run(self):
        while not self.finished.is_set():
            self.function(*self.args, **self.kwargs)
            self.finished.wait(self.interval)
t = RepeatingTimer(10.0, hello)
t.start()

重點研究 RepeatingTimer 類,它繼承了 threading._Timer,可是重寫了父類的 run 方法。這是 Python2 的寫法,python3 中 RepeatingTimer 應該繼承 threading.Timer

爲何要重寫 Threadrun 方法?

_Timer 是一個 Thread 子類,咱們先看看 Thread 類的 run 用法。

from threading import Thread
def hello():
     print "hello, world"
# 繼承 Thread
class MyThread(Thread):
    # 把要執行的代碼寫到run函數裏面 線程在建立後會直接運行run函數
    def run(self):
        hello()
t = MyThread()
t.start()

Thread 對象的完整定義:

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})

其中 run 方法代碼:

class Thread(_Verbose):
    def run(self):
        try:
            if self.__target:
                self.__target(*self.__args, **self.__kwargs)
        finally:
            # Avoid a refcycle if the thread is running a function with
            # an argument that has a member that points to the thread.
            del self.__target, self.__args, self.__kwargs

標準的 run 方法用於執行用戶傳入構造函數的 target 方法。 子類能夠重寫 run 方法,把要執行的代碼寫到 run 裏面,線程在建立後,用戶調用 start() 方法會運行 run() 方法。

因此 RepeatingTimer 重寫 _Timer 的 run() 方法,能夠改變線程的執行體,當咱們調用 RepeatingTimer 的 start() 方法時會執行咱們重寫的 run() 方法。

再看看 RepeatingTimer 類中的 while not self.finished.is_set() 語句,self.finished.is_set() 直到 True 纔會退出循環,定時器才結束。finishedthreading.Event 對象。一個 Event 對象管理着一個 flag 標誌,它能被 set() 方法設置爲 True,也能被 clear() 方法設置爲 False,調用 wait([timeout]) 線程會一直 sleep 到 flag 爲 True 或超時時間到達。

咱們知道定時器有一個 cancel() 方法能夠提早取消操做。它實際上是調用 Event.clear() 方法提早讓 wait 方法結束等待,而且判斷在 flag 爲 true 的狀況下不執行定時器操做。具體的代碼:

class _Timer(Thread):
    """Call a function after a specified number of seconds:
            t = Timer(30.0, f, args=[], kwargs={})
            t.start()
            t.cancel() # stop the timer's action if it's still waiting
    """

    def __init__(self, interval, function, args=[], kwargs={}):
        Thread.__init__(self)
        self.interval = interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.finished = Event()

    def cancel(self):
        """Stop the timer if it hasn't finished yet"""
        self.finished.set()

    def run(self):
        self.finished.wait(self.interval)
        if not self.finished.is_set():
            self.function(*self.args, **self.kwargs)
        self.finished.set()

因此 RepeatingTimer 的 run 方法會一直執行 while 循環體,在循環體了會執行用戶傳入的 function 對象,並等待指定的時間。當用戶想退出定時器時,只須要調用 cancel 方法,將 flag 置爲 True 便不會繼續執行循環體了。這樣便完成了一個還不錯的循環定時器。

相關文章
相關標籤/搜索