Уведомления

Группа в Telegram: @pythonsu

#1 Июнь 7, 2010 15:32:50

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

Таймер

Была у меня задумка, использовать таймер, не зависящий от 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-шных классов.

Вопросы: есть ли более элегантное решение? Корректно ли такое использование потоков?



Офлайн

#2 Июнь 7, 2010 18:16:51

igor.kaist
От:
Зарегистрирован: 2007-11-12
Сообщения: 1879
Репутация: +  3  -
Профиль   Отправить e-mail  

Таймер

Таймер в GUI библиотеках вроде не на дополнительном потоке основан, а на асинхронности, поэтому при использовании вашего таймера в GUI приложениях возможны глюки.



Офлайн

#3 Июнь 26, 2010 12:37:51

belk_o
От:
Зарегистрирован: 2010-05-23
Сообщения: 17
Репутация: +  0  -
Профиль   Отправить e-mail  

Таймер

можно пользовать модуль gobject - там есть такое



Офлайн

#4 Июнь 26, 2010 13:59:28

bw
От:
Зарегистрирован: 2007-09-26
Сообщения: 938
Репутация: +  20  -
Профиль   Адрес электронной почты  

Таймер

У GUI библиотек как правило есть свои таймеры. Так же, по крайней мере это касается GTK/GLib, существует особенность использования потоков (еще по форуму про gobject, gtk и потоки).

..bw



Офлайн

#5 Июнь 26, 2010 21:12:36

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

Таймер

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

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



Офлайн

#6 Июнь 27, 2010 11:21:58

bw
От:
Зарегистрирован: 2007-09-26
Сообщения: 938
Репутация: +  20  -
Профиль   Адрес электронной почты  

Таймер

> Однако, тема была создана сравнительно давно.
Здесь много глупых создается, так что фильтрую я достаточно грубо и интересные вопросы частенько пропускаю, увы.

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

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

..bw



Офлайн

#7 Июнь 30, 2010 13:52:46

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

Таймер

Доделал вариант и с 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()



Офлайн

#8 Июль 14, 2010 21:18:56

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

Таймер

Работа над ошибками. Написал про защиту в 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()



Офлайн

#9 Март 24, 2011 21:58:32

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

Таймер

По некоторым соображениям решил продолжить тему. У предложенного таймера есть недостаток - он “плывёт”. То есть, накапливает погрешности, вызванные несовершенством ОС и, возможно, также вызванные 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(). Но процессы не разделяют данные, потому будет трудно передать в качестве функции процесса метод из какого-нибудь объекта родительского процесса. Как я понимаю, асинхронности не получится.



Офлайн

#10 Март 24, 2011 22:42:12

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

Таймер

Не нужно перезапускать поток. Это очень накладно. threding.Timer сделан на питоне - вы тоже сможете повторить.
datetime.datetime.now - classmethod



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version