在线程中是否有一种优雅的或python式的方式来中断time.sleep()调用?
下面的代码按照我期望的方式工作,即:在线程中是否有一种优雅的或python式的方式来中断time.sleep()调用?,python,multithreading,Python,Multithreading,下面的代码按照我期望的方式工作,即: 有一个从1到8计数的QThread(“Ernie”),在计数之间休眠1秒 有一个免费的UI小部件(“Bert”) 在正常操作下,程序运行直到线程完成并且UI关闭 Ctrl-C键盘中断将在正常完成之前正常停止程序 要做到这一点,我必须将1秒睡眠分解为50毫秒的块,以检查标志 有没有一种更像python的方式可以在线程中睡眠一段时间(例如1秒),但可以被某个标志或信号打断? try: for i in xrange(8
- 有一个从1到8计数的QThread(“Ernie”),在计数之间休眠1秒
- 有一个免费的UI小部件(“Bert”)
- 在正常操作下,程序运行直到线程完成并且UI关闭
- Ctrl-C键盘中断将在正常完成之前正常停止程序
try:
for i in xrange(8):
print "i=%d" % i
for _ in xrange(20):
time.sleep(0.05)
if not self.running:
raise GracefulShutdown
except GracefulShutdown:
print "ernie exiting"
我宁愿这样做,并以某种方式在线程中导致GracefulShutdown异常:
try:
for i in xrange(8):
print "i=%d" % i
time.sleep(1)
# somehow allow another thread to raise GracefulShutdown
# during the sleep() call
except GracefulShutdown:
print "ernie exiting"
完整程序
from PySide import QtCore, QtGui
from PySide.QtGui import QApplication
import sys
import signal
import time
class GracefulShutdown(Exception):
pass
class Ernie(QtCore.QThread):
def __init__(self):
super(Ernie, self).__init__()
self.running = True
def run(self):
try:
for i in xrange(8):
print "i=%d" % i
for _ in xrange(20):
time.sleep(0.05)
if not self.running:
raise GracefulShutdown
except GracefulShutdown:
print "ernie exiting"
def shutdown(self):
print "ernie received request to shutdown"
self.running = False
class Bert(object):
def __init__(self, argv):
self.app = QApplication(argv)
self.app.quitOnLastWindowClosed = False
def show(self):
widg = QtGui.QWidget()
widg.resize(250, 150)
widg.setWindowTitle('Simple')
widg.show()
self.widg = widg
return widg
def shutdown(self):
print "bert exiting"
self.widg.close()
def start(self):
# return control to the Python interpreter briefly every 100 msec
timer = QtCore.QTimer()
timer.start(100)
timer.timeout.connect(lambda: None)
return self.app.exec_()
def handleInterrupts(*actors):
def handler(sig, frame):
print "caught interrupt"
for actor in actors:
actor.shutdown()
signal.signal(signal.SIGINT, handler)
bert = Bert(sys.argv)
gratuitousWidget = bert.show()
ernie = Ernie()
ernie.start()
handleInterrupts(bert, ernie)
retval = bert.start()
print "bert finished"
while not ernie.wait(100):
# return control to the Python interpreter briefly every 100 msec
pass
print "ernie finished"
sys.exit(retval)
我的本能是用os.kill发出信号,但只有主线程接收信号,所以Ernie不能被那样打断。文档建议改为使用锁 我的想法是创建一个只有在杀死厄尼的时候才能访问的锁。在主线程创建Bert和Ernie之后,将创建并锁定一个锁文件。然后,厄尼将花整整一秒钟的时间试图得到锁,而不是睡上一秒钟。一旦程序关闭,你可以释放锁,厄尼会立即得到锁;这告诉厄尼是时候关机了 由于您无法按我们所希望的方式集成信号和线程,下面是另一篇关于线程锁定超时的帖子:
我无法告诉你这个解决方案有多像蟒蛇,因为我仍在努力理解蟒蛇到底意味着什么。一旦你开始引入线程,优雅的代码就变得越来越难写了,无论如何。我不确定它有多像python,但它是有效的。只需使用队列并使用带超时的阻塞get。请参见下面的示例:
import threading
import Queue
import time
q = Queue.Queue()
def workit():
for i in range(10):
try:
q.get(timeout=1)
print '%s: Was interrupted' % time.time()
break
except Queue.Empty:
print '%s: One second passed' % time.time()
th = threading.Thread(target=workit)
th.start()
time.sleep(3.2)
q.put(None)
通常,
SIGINT
会中断time.sleep
调用,但Python只允许应用程序的主线程接收信号,因此不能在这里使用。我建议尽可能避免时间。睡眠,并使用QTimer
from PySide import QtCore, QtGui
from PySide.QtCore import QTimer
from PySide.QtGui import QApplication
import sys
import signal
from functools import partial
class Ernie(QtCore.QThread):
def __init__(self):
super(Ernie, self).__init__()
def do_print(self, cur_num, max_num):
print "i=%d" % cur_num
cur_num += 1
if cur_num < max_num:
func = partial(self.do_print, cur_num, max_num)
QTimer.singleShot(1000, func)
else:
self.exit()
def run(self):
self.do_print(0, 8)
self.exec_() # QTimer requires the event loop for the thread be running.
print "ernie exiting"
class Bert(object):
def __init__(self, argv):
self.app = QApplication(argv)
self.app.quitOnLastWindowClosed = False
def show(self):
widg = QtGui.QWidget()
widg.resize(250, 150)
widg.setWindowTitle('Simple')
widg.show()
self.widg = widg
return widg
def shutdown(self):
print "bert exiting"
self.widg.close()
def start(self):
# return control to the Python interpreter briefly every 100 msec
timer = QtCore.QTimer()
timer.start(100)
timer.timeout.connect(lambda: None)
return self.app.exec_()
def handleInterrupts(*actors):
def handler(sig, frame):
print "caught interrupt"
for actor in actors:
actor.shutdown()
signal.signal(signal.SIGINT, handler)
bert = Bert(sys.argv)
gratuitousWidget = bert.show()
ernie = Ernie()
ernie.start()
handleInterrupts(bert)
retval = bert.start()
print "bert finished"
ernie.exit()
while not ernie.wait(100):
# return control to the Python interpreter briefly every 100 msec
pass
print "ernie finished"
sys.exit(retval)
coroutine
允许修饰函数在出现yield
时,在给定的时间内将控制权交还给Qt事件循环,然后恢复修饰方法的执行。诚然,这实际上只是改变了我最初示例的复杂性,但它确实将复杂性隐藏在您试图在线程中执行的实际工作之外
工作原理:
该方法受异步库(如和模块)中的协同路由实现的启发。虽然我没有尝试提出像这些一样强大的东西,但想法是一样的。我们希望能够中断的方法被实现为生成器,并用一个decorator进行修饰,该decorator知道如何以允许正确暂停/恢复生成器的方式调用和接收来自生成器的响应。调用do\u print
时的流程基本上如下所示:
do\u print()
是从run
调用的。这实际上会导致调用coroutine.wrapper
wrapper
调用实际的do\u print
,它返回一个生成器对象。它将该对象传递给execute
李>
execute
调用生成器对象上的next
。这将导致do_print
运行,直到达到产量
。然后暂停执行do_print
execute
schedulesdo\u print
恢复执行。它首先通过使用上次运行的do\u print
迭代中的值yield
ed,或者通过默认值0(将执行安排为立即恢复)来确定何时进行调度。它调用QTimer.singleShot
来计划自己在timeout
毫秒内再次运行,使用partial
,这样它也可以传递生成器对象
重复步骤3-4,直到do\u print
停止让步,调用self.exit()
并返回,此时会引发StopIteration
,而coroutine
装饰程序只是返回,而不是安排另一个execute
调用
+1由于队列是一种已知的健壮的进程间/线程间通信机制,我明白你的意思,但实现细节似乎使其过于复杂。如果我能处理协作多路复用或状态机方法,我根本不会为线程而烦恼。。。但是它使设计变得简单易懂。@JasonS QT项目的网站实际上明确指出,使用线程和睡眠
来实现计时器是一种很好的方法。他们基本上主张使用QTimer
,而不引入QThread
。因此,对于您的特定示例,我认为首选的方法是完全避免QThread
。当然,如果你正在做一些CPU密集型的事情,这会改变一切。好吧,这里的“sleep()”调用实际上是任何类型的长操作的代理,这些长操作不受CPU限制,也不容易从内到外转换为事件驱动的方法:在复杂的事件序列中等待网络数据、等待I/O等。是的,我可以使用QTimer和状态机,但状态机方法使设计/测试/审查/等变得非常糟糕。所以,是的,从并发性的角度来看,QTimer方法会更好,但特定领域的优先级胜过这一点。@JasonS公平。我对我的答案进行了编辑,加入了一种基于协同程序的方法,这种方法可能会更好地满足您的需求,因为它将使用计时器的大部分复杂性转移到别处
def coroutine(func):
def wrapper(*args, **kwargs):
def execute(gen):
try:
op = gen.next() # run func until a yield is hit
# Determine when to resume execution of the coroutine.
# If func didn't yield anything, schedule it to run again
# immediately by setting timeout to 0.
timeout = op or 0
func = partial(execute, gen)
QTimer.singleShot(timeout, func) # This schedules execute to run until the next yield after `timeout` milliseconds.
except StopIteration:
return
gen = func(*args, **kwargs) # Get a generator object for the decorated function.
execute(gen)
return wrapper
def async_sleep(timeout):
""" When yielded inside a coroutine, triggers a `timeout` length sleep. """
return timeout
class Ernie(QtCore.QThread):
def __init__(self):
super(Ernie, self).__init__()
self.cur_num = 0
self.max_num = 8
@coroutine
def do_print(self):
for i in range(8):
print "i=%d" % i
yield async_sleep(1000) # This could also just be yield 1000
self.exit()
def run(self):
self.do_print() # Schedules do_print to run once self.exec_() is run.
self.exec_()
print "ernie exiting"