Найти - Пользователи
Полная версия: Таймер
Начало » Python для новичков » Таймер
1 2
Kogrom
Была у меня задумка, использовать таймер, не зависящий от GUI. В библиотеках, идущих с Питоном, не нашёл в чистом виде, поэтому сделал такой велосипед:

import threading

class Timer:
def __init__(self, function, period = 1.0):
self.func = function
self.period = period
self.timer = None

def start(self):
if self.timer == None:
self.timer = threading.Timer(self.period, function=self.on_timeout)
self.timer.start()

def on_timeout(self):
self.func()
self.timer.run()

def stop(self):
self.timer.cancel()
self.timer = None

if __name__ == '__main__':

def func():
print "Hi!"

timer = Timer(func, 0.5)
timer.start()
raw_input("press Enter for end first step...\n")
timer.stop()
raw_input("press Enter for start second step...\n")
timer.start()
raw_input("press Enter if you want to stop...\n")
timer.stop()
Однако, не уверен, что такой код корректен, так как во первых, у меня не очень большой опыт работы с питоновскими потоками, во вторых, оно у меня плохо стыкуется с библиотеками GUI (wxPython в Ubuntu выдает ошибку). Хотя, возможно, я сам некорректно обращаюсь к методам GUI-шных классов.

Вопросы: есть ли более элегантное решение? Корректно ли такое использование потоков?
igor.kaist
Таймер в GUI библиотеках вроде не на дополнительном потоке основан, а на асинхронности, поэтому при использовании вашего таймера в GUI приложениях возможны глюки.
belk_o
можно пользовать модуль gobject - там есть такое
bw
У GUI библиотек как правило есть свои таймеры. Так же, по крайней мере это касается GTK/GLib, существует особенность использования потоков (еще по форуму про gobject, gtk и потоки).

..bw
Kogrom
bw
У GUI библиотек как правило есть свои таймеры.
Это я знаю. Но мне хотелось использовать единиый таймер и для консольного приложения и для любого GUI.

Однако, тема была создана сравнительно давно. Я выбрал как раз тот таймер, что есть в GUI, и пока успокоился.
bw
Так же, по крайней мере это касается GTK/GLib, существует особенность использования потоков (еще по форуму про gobject, gtk и потоки).
Пока только это нашёл.
http://python.su/forum/viewtopic.php?id=2101
Но тут конкретные, привязанные к конкретной библиотеке функции. Правильнее было бы, если бы Питон решал это стандартным универсальным способом.
bw
> Однако, тема была создана сравнительно давно.
Здесь много глупых создается, так что фильтрую я достаточно грубо и интересные вопросы частенько пропускаю, увы.

> Пока только это нашёл.
Ну я и имел ввиду gobject.threads_init.

> Правильнее было бы
Иметь джина в ночном горшке :-).

..bw
Kogrom
Доделал вариант и с wxPython. В общем, таймер остался почти тот же, но в GUI-шной части пришлось ставить защиту. Интересно, что в моей системе (Ubuntu 9.10) погрешность таймера около 1 миллисекунды, при чём, большая часть этой погрешности - постоянная. Так что можно провести калибровку, которая повысит точность ещё в 10 раз.

import threading


class Timer:

def __init__(self, function):
self._func = function
self._timer = None

def start(self, period=1.0):
if self._timer == None:
self._timer = threading.Timer(period, function=self.on_timeout)
self._timer.start()

def on_timeout(self):
self._func()
self._timer.run()

def is_running(self):
return bool(self._timer)

def stop(self):
if self._timer:
self._timer.cancel()
self._timer = None


if __name__ == '__main__':

import wx
import datetime


class MyFrame(wx.Frame):

def __init__(self):

wx.Frame.__init__(self, None, title="Timer with GUI",
size=(300, 600))
panel = wx.Panel(self)
startBtn = wx.Button(panel, -1, "Start timer")
stopBtn = wx.Button(panel, -1, "Stop timer")
clearBtn = wx.Button(panel, -1, "Clear")

self.log = wx.TextCtrl(panel, -1, "",
style=wx.TE_RICH|wx.TE_MULTILINE)
inner = wx.BoxSizer(wx.HORIZONTAL)
inner.Add(startBtn, 0, wx.RIGHT, 15)
inner.Add(stopBtn, 0, wx.RIGHT, 15)
inner.Add(clearBtn, 0, wx.RIGHT, 15)
main = wx.BoxSizer(wx.VERTICAL)
main.Add(inner, 0, wx.ALL, 5)
main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(main)
self.Bind(wx.EVT_BUTTON, self.on_start_button, startBtn)
self.Bind(wx.EVT_BUTTON, self.on_stop_button, stopBtn)
self.Bind(wx.EVT_BUTTON, self.on_clear_button, clearBtn)
self.Bind(wx.EVT_CLOSE, self.on_close_window)
self.timer = Timer(self.ret_func)

def on_start_button(self, evt):
self.timer.start(0.1)

def on_stop_button(self, evt):
self.timer.stop()

def on_clear_button(self, evt):
self.log.Clear()

def ret_func(self):
self.log.AppendText("timer: %s\n" %
datetime.datetime(2000, 1, 1).now())

def on_close_window(self, evt):
self.timer.stop()
self.Destroy()

app = wx.PySimpleApp()
frm = MyFrame()
frm.Show()
app.MainLoop()
Kogrom
Работа над ошибками. Написал про защиту в GUI, но не поставил. Кроме того, мой способ оживления таймера очень не надёжен. Лучше уж тупо воссоздавать поток. Такой вариант менее изящен, но вроде бы не глючит:

import threading


class Timer:

def __init__(self, function):
self._func = function
self._timer = None
self._period = 1.0
self._isRunning = False

def start(self, period):
if self._timer == None:
self._timer = threading.Timer(period, function=self.on_timeout)
self._timer.start()
self._period = period
self._isRunning = True

def on_timeout(self):
self._func()
self.stop()
self.start(self._period)

def is_running(self):
return self._isRunning

def stop(self):
if self._timer:
self._timer.cancel()
self._timer = None
self._isRunning = False


if __name__ == '__main__':

import wx
import datetime


class MyFrame(wx.Frame):

def __init__(self):

wx.Frame.__init__(self, None, title="Timer with GUI",
size=(300, 600))
panel = wx.Panel(self)
startBtn = wx.Button(panel, -1, "Start timer")
stopBtn = wx.Button(panel, -1, "Stop timer")
clearBtn = wx.Button(panel, -1, "Clear")

self.log = wx.TextCtrl(panel, -1, "",
style=wx.TE_RICH|wx.TE_MULTILINE)
inner = wx.BoxSizer(wx.HORIZONTAL)
inner.Add(startBtn, 0, wx.RIGHT, 15)
inner.Add(stopBtn, 0, wx.RIGHT, 15)
inner.Add(clearBtn, 0, wx.RIGHT, 15)
main = wx.BoxSizer(wx.VERTICAL)
main.Add(inner, 0, wx.ALL, 5)
main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(main)
self.Bind(wx.EVT_BUTTON, self.on_start_button, startBtn)
self.Bind(wx.EVT_BUTTON, self.on_stop_button, stopBtn)
self.Bind(wx.EVT_BUTTON, self.on_clear_button, clearBtn)
self.Bind(wx.EVT_CLOSE, self.on_close_window)
self.timer = Timer(self.ret_func)

def on_start_button(self, evt):
self.timer.start(0.02)

def on_stop_button(self, evt):
self.timer.stop()

def on_clear_button(self, evt):
self.log.Clear()

def ret_func(self):
wx.CallAfter(self._ret_func)

def _ret_func(self):
if self.log.GetNumberOfLines() > 20:
self.log.Clear()
self.log.AppendText("timer: %s\n" %
datetime.datetime(2000, 1, 1).now())

def on_close_window(self, evt):
self.timer.stop()
self.Destroy()

app = wx.PySimpleApp()
frm = MyFrame()
frm.Show()
app.MainLoop()
Kogrom
По некоторым соображениям решил продолжить тему. У предложенного таймера есть недостаток - он “плывёт”. То есть, накапливает погрешности, вызванные несовершенством ОС и, возможно, также вызванные GIL. Так как (на первый взгляд) это плавание легко удалить, то я сделаю это.

import threading
import time


class Timer:

def __init__(self, function):
self._func = function
self._isRunning = False

def start(self, period, startWithJerk=False):
if self._isRunning == False:
self._currentTime = time.time()
self._start_new_period(period)
if startWithJerk: self._func()
self._period = period
self._isRunning = True

def _start_new_period(self, period):
self._timer = threading.Timer(period, function=self._on_timeout)
self._timer.start()

def _on_timeout(self):
if self._isRunning:
self._currentTime += self._period
period = self._period - (time.time() - self._currentTime)
while period <= 0:
period += self._period
self._start_new_period(period)
self._func()

def is_running(self):
return self._isRunning

def stop(self):
if self._isRunning:
self._timer.cancel()
self._timer.join()
self._timer = None
self._isRunning = False


if __name__ == '__main__':

import wx
import datetime


class MyFrame(wx.Frame):

def __init__(self):

wx.Frame.__init__(self, None, title="Timer with GUI",
size=(300, 600))
panel = wx.Panel(self)
startBtn = wx.Button(panel, -1, "Start timer")
stopBtn = wx.Button(panel, -1, "Stop timer")
clearBtn = wx.Button(panel, -1, "Clear")

self.log = wx.TextCtrl(panel, -1, "",
style=wx.TE_RICH|wx.TE_MULTILINE)
inner = wx.BoxSizer(wx.HORIZONTAL)
inner.Add(startBtn, 0, wx.RIGHT, 15)
inner.Add(stopBtn, 0, wx.RIGHT, 15)
inner.Add(clearBtn, 0, wx.RIGHT, 15)
main = wx.BoxSizer(wx.VERTICAL)
main.Add(inner, 0, wx.ALL, 5)
main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(main)
self.Bind(wx.EVT_BUTTON, self.on_start_button, startBtn)
self.Bind(wx.EVT_BUTTON, self.on_stop_button, stopBtn)
self.Bind(wx.EVT_BUTTON, self.on_clear_button, clearBtn)
self.Bind(wx.EVT_CLOSE, self.on_close_window)
self.timer = Timer(self.ret_func)

def on_start_button(self, event):
self.timer.start(0.1)

def on_stop_button(self, event):
self.timer.stop()

def on_clear_button(self, event):
self.log.Clear()

def ret_func(self):
wx.CallAfter(self._ret_func)

def _ret_func(self):
if self.log.GetNumberOfLines() > 50:
self.log.Clear()
self.log.AppendText("timer: %s\n" %
datetime.datetime(2000, 1, 1).now())

def on_close_window(self, event):
self.timer.stop()
self.Destroy()

app = wx.PySimpleApp()
frm = MyFrame()
frm.Show()
app.MainLoop()
В моих тестах “плавание” прекратилось. Но проверка была на одном компьютере и с одним таймером. Пока мне этого достаточно.

Вместо циклического создания threading.Timer можно использовать один поток, в котором будет цикл со sleep. Но у threading.Timer есть полезный метод cancel(), позволяющий быстро остановить таймер. С join не выйдет быстро остановить таймер - надо будет дождаться окончания sleep.

Цикл со sleep можно использовать в multiprocessing.Process, если реализовывать таймер на нём. Вероятно, там уместно будет использовать terminate(). Но процессы не разделяют данные, потому будет трудно передать в качестве функции процесса метод из какого-нибудь объекта родительского процесса. Как я понимаю, асинхронности не получится.
Андрей Светлов
Не нужно перезапускать поток. Это очень накладно. threding.Timer сделан на питоне - вы тоже сможете повторить.
datetime.datetime.now - classmethod
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB