[英]Is there a graceful or Pythonic way to interrupt a time.sleep() call in a thread?
下面的代码按我期望的方式工作,即:
要做到这一点,我不得不将1秒钟的睡眠分解为50毫秒的块状物来检查旗帜。
是否有更多的Pythonic方式在线程中睡眠一段时间(例如1秒)但是可以通过某些标志或信号中断?
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)
我不确定它是如何Pythonic但它的工作原理。 只需使用队列并使用阻塞获取超时。 请参阅以下示例:
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只允许信号由应用程序的主线程接收,因此不能在此处使用。 我会建议避免time.sleep
如果可能的话,并利用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)
我们不是让run()
方法使用time.sleep
运行for循环,而是在线程内部启动一个事件循环,并使用QTimer
以设定的间隔执行所需的打印。 这样,只要我们想要线程关闭,我们就可以调用bernie.exit()
,这将导致bernie'
的事件循环立即关闭。
编辑:
这是实现同样想法的另一种方法,至少隐藏了一些复杂性,允许原始for循环保持不变:
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"
coroutine
允许装饰函数在出现yield
时将控制权返回给Qt事件循环一段给定的时间,然后恢复执行装饰方法。 当然,这实际上只是将复杂性从原始示例中移除,但它确实将其隐藏在您尝试在线程中完成的实际工作中。
这个怎么运作:
该方法的灵感来自异步库中的协程实现,例如Tornado和asyncio模块。 虽然我没有尝试提出像那些强大的东西,但这个想法是一样的。 我们希望能够中断的方法被实现为生成器,并且装饰有装饰器,装饰器知道如何以允许正确暂停/恢复生成器的方式调用和接收来自生成器的响应。 调用do_print
时的流程基本上是这样的:
run
调用do_print()
。 这实际上会导致调用coroutine.wrapper
。 wrapper
调用真实的do_print
,它返回一个生成器对象。 它传递该对象以execute
。 execute
next
调用。 这会导致do_print
运行,直到达到yield
。 然后暂停执行do_print
。 execute
schedule do_print
以恢复执行。 它通过首先确定何时调度它来执行此操作,方法是使用上一次运行的do_print
迭代中的do_print
yield
ed,或者通过默认为0(将执行调度为立即恢复)。 它调用QTimer.singleShot
来安排自己在timeout
毫秒内再次运行,使用partial
因此它也可以传递生成器对象。 do_print
停止产生,调用self.exit()
并返回,此时引发StopIteration
,并且coroutine
装饰器只返回而不是调度另一个execute
调用。 我的直觉是用os.kill发出一个信号,但只有主线程才会收到信号,所以厄尼不能这样打断。 文档建议使用锁。
我的想法是创建一个只有在杀死Ernie时才能访问的锁。 在主线程创建Bert和Ernie之后,会创建并锁定锁定文件。 然后,厄尼将花费一整秒的时间试图获得锁定,而不是睡一秒钟。 一旦程序关闭,您就可以释放锁定,Ernie会立即获得锁定; 这告诉厄尼,是时候关闭了。
既然你不能按照我们想要的方式集成信号和线程,那么这是另一篇关于线程锁定超时的帖子:
我无法告诉你Pythonic这个解决方案是怎么回事,因为我仍然想要了解Pythonic究竟是什么。 一旦你开始介绍线程,优雅的代码在任何情况下都会变得越来越难写。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.