我在后台线程中有一个带有长时间运行的mainloop的线程python应用程序。这个后台主循环实际上是对pyglet.app.run()的调用,它驱动一个GUI窗口,也可以配置为定期调用其他代码。我需要从主线程中随意调用do_stuff(duration)
函数来触发GUI中的动画,等待动画停止,然后返回。实际动画必须在后台线程中完成,因为GUI库无法处理由单独的线程驱动。
我相信我需要做这样的事情:
import threading
class StuffDoer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.max_n_times = 0
self.total_n_times = 0
self.paused_ev = threading.Event()
def run(self):
# this part is outside of my control
while True:
self._do_stuff()
# do other stuff
def _do_stuff(self):
# this part is under my control
if self.paused_ev.is_set():
if self.max_n_times > self.total_n_times:
self.paused_ev.clear()
else:
if self.total_n_times >= self.max_n_times:
self.paused_ev.set()
if not self.paused_ev.is_set():
# do stuff that must execute in the background thread
self.total_n_times += 1
sd = StuffDoer()
sd.start()
def do_stuff(n_times):
sd.max_n_times += n_times
sd.paused_ev.wait_for_clear() # wait_for_clear() does not exist
sd.paused_ev.wait()
assert (sd.total_n_times == sd.max_n_times)
编辑:使用max_n_times
代替stop_time
来澄清为什么Thread.join(duration)
无法解决问题。
来自threading.Event的文档:
等待([超时])
阻止内部标志为真。 如果进入时内部标志为真, 马上回来。否则,阻止 直到另一个线程调用set() 将标志设置为true,或者直到 发生可选超时。
如果我有一对事件paused_ev
和not_paused_ev
,并且使用not_paused_ev.wait()
,我发现我可以获得我正在寻找的行为。我几乎只能使用Thread.join(duration)
,除非它只需要在后台线程实际注册时间到时才准确返回。是否应该使用其他同步对象或其他策略?
我也愿意接受这样的论点,即如果他们是一个好的论据,我会以错误的方式接近这一切。
答案 0 :(得分:3)
希望我从我的评论中获得一些修订或其他信息,但我有点想知道你是不是通过继承Thread来过度工作。你可以这样做:
class MyWorker(object):
def __init__(self):
t = Thread(target = self._do_work, name "Worker Owned Thread")
t.daemon = True
t.start()
def _do_work(self):
While True:
# Something going on here, forever if necessary. This thread
# will go away if the other non-daemon threads terminate, possibly
# raising an exception depending this function's body.
我发现当你想要运行的方法更适合某个其他类的成员函数而不是作为线程上的run方法时,这更有意义。此外,这使您不必在Thread内部封装一堆业务逻辑。当然,所有的IMO。
答案 1 :(得分:1)
您的GUI动画线程似乎在其while True
循环中使用自旋锁。使用线程安全队列可以防止这种情况。根据我对你的问题的解读,这种方法在功能上是等效和有效的。
我省略了上面代码的一些细节,这些细节不会改变。我也在这里假设你不控制的run()方法使用self.stop_time值来完成它的工作;否则就不需要线程安全队列。
from Queue import Queue
from threading import Event
class StuffDoer:
def __init__(self, inq, ready):
self.inq = inq
self.ready = ready
def _do_stuff(self):
self.ready.set()
self.stop_time = self.inq.get()
GUIqueue = Queue()
control = Event()
sd = StuffDoer(GUIqueue, control)
def do_stuff(duration):
control.clear()
GUIqueue.put(time.time() + duration)
control.wait()
答案 2 :(得分:1)
我最终使用类似于@wberry建议的队列,并使用Queue.task_done
和Queue.wait
:
import Queue
import threading
class StuffDoer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.setDaemon(True)
self.max_n_times = 0
self.total_n_times = 0
self.do_queue = Queue.Queue()
def run(self):
# this part is outside of my control
while True:
self._do_stuff()
# do other stuff
def _do_stuff(self):
# this part is under my control
if self.total_n_times >= self.max_n_times:
try:
self.max_n_times += self.do_queue.get(block=False)
except Queue.Empty, e:
pass
if self.max_n_times > self.total_n_times:
# do stuff that must execute in the background thread
self.total_n_times += 1
if self.total_n_times >= self.max_n_times:
self.do_queue.task_done()
sd = StuffDoer()
sd.start()
def do_stuff(n_times):
sd.do_queue.put(n_times)
sd.do_queue.join()
assert (sd.total_n_times == sd.max_n_times)
答案 3 :(得分:0)
我根据@g.d.d.c advice针对此问题制定了解决方案。有我的代码:
threads = []
# initializing aux thread(s) in the main thread ...
t = threading.Thread(target=ThreadF, args=(...))
#t.setDaemon(True) # I'm not sure does it really needed
t.start()
threads.append(t.ident)
# Block main thread
while filter(lambda thread: thread.ident in threads, threading.enumerate()):
time.sleep(10)
此外,您可以使用Thread.join
来阻止主线程 - 这是更好的方法。