wxPython线程阻塞
wxPython threads blocking
这是 wxPython 的 Phoenix 分支。
为了不阻塞 GUI,我正在尝试 运行 几个线程。
我的两个线程工作正常,但另一个线程似乎从未达到其绑定结果函数。我可以看出它是 运行ning,它似乎没有正确 post 事件。
这是主要计算线程的结果函数:
def on_status_result(self, event):
if not self.panel.progress_bar.GetRange():
self.panel.progress_bar.SetRange(event.data.parcel_count)
self.panel.progress_bar.SetValue(event.data.current_parcel)
self.panel.status_label.SetLabel(event.data.message)
这是我绑定它们的方式:
from wx.lib.pubsub.core import Publisher
PUB = Publisher()
这是我绑定方法的方式:
def post_event(message, data):
wx.CallAfter(lambda *a: Publisher().sendMessage(message, data=data))
这是线程。第一个不起作用,但后两个起作用:
class PrepareThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
for status in prepare_collection(DATABASE, self._previous_id, self._current_id, self._year, self._col_type,
self._lock):
post_event('prepare.running', status)
post_event('prepare.complete', None)
return None
def abort(self):
self._want_abort = True
class SetupThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
do_more_stuff_with_the_database()
return None
def abort(self):
self._want_abort = True
class LatestCollectionsThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
do_stuff_with_my_database()
return None
def abort(self):
self._want_abort = True
prepare_collection
是一个生成 Status
对象的函数,如下所示:
class Status:
def __init__(self, parcel_count, current_parcel, total, message):
self.parcel_count = parcel_count
self.current_parcel = current_parcel
self.total = total
self.message = message
我是这样 creating/starting/subscribing PrepareThread 的:
MainForm(wx.Form):
prepare_thread = PrepareThread(self)
prepare_thread.start()
self.pub = Publisher()
self.pub.subscribe(self.on_status_result, 'prepare.running')
self.pub.subscribe(self.on_status_result, 'prepare.complete')
def on_status_result(self, event):
if not self.panel.progress_bar.GetRange():
self.panel.progress_bar.SetRange(event.data.parcel_count)
self.panel.progress_bar.SetValue(event.data.current_parcel)
self.panel.status_label.SetLabel(event.data.message)
我已经尝试用 range(10)
删除 prepare_collection
,但我仍然没有点击事件处理程序。
问题是事件系统最终会从线程本身调用更新函数(事件处理程序),你几乎不应该这样做(基本上你最终会遇到奇怪的竞争条件和工件)......总是在主线程中进行回调。
wxPython 已经考虑到这一点,任何用 wx.CallAfter 调用的方法都将从主程序循环中调用,主程序循环总是在主线程中 运行。这与 wx.pubsub 模块相结合,使您可以轻松创建自己的事件框架……像这样
def MyPostEvent(event_name,event_data):
#just a helper that triggers the event with wx.CallAfter
wx.CallAfter(lambda *a:Publisher().sendMessage(event_name,data=event_data))
#then to post an event
MyPostEvent("some_event.i_made_up",{"payload":True})
#then in your main thread subscribe
def OnEventHandler(evt):
print "EVT.data",evt.data
pub = Publisher()
pub.subscribe("some_event.i_made_up",OnEventHandler)
这可能是一个相当复杂的答案,要准确计算出代码的每个部分中有哪些片段(即它们都位于哪些文件中)有点困难。我假设您想保留执行此操作的 pubsub
方法(我认为这是个好主意)。如果您的真实项目的结构非常复杂,您可能需要比我在这里使用的更复杂的 Publisher
管理 - 让我知道...
开始 - 我会先剧透 - 这里有一个 one file solution 你似乎想要的东西 - 一个带有按钮的面板,用于启动准备线程、状态栏和准备时的处理程序完成的。在 Python 2.7 上使用 wxPython Phoenix 64 位 Python 3 和老式的 wxPython 进行了测试。两者都在 Windows - 但如有必要,我可以在 Linux 框中旋转它。
总结该文件的重要(非样板)部分
您需要一个单独的 Publisher
对象,您的线程将消息发送到该对象并且您的主线程(我猜在您的示例中为 MainForm
)订阅。您可以为每个线程管理一个 Publisher
,但我认为这里您只需要一个用于 PrepareThread
,所以我现在将使用该模型。
在文件的顶部,使用
from wx.lib.pubsub import pub
这让 pubsub
管理实例化单个 Publisher
对象。
在你的线程中,正如你所做的那样,在那里发布消息 - 对你的 post_event
助手的一个小修正:
def post_event(message, data):
wx.CallAfter(lambda *a: pub.sendMessage(message, data=data))
在您的主线程中 - 订阅这些消息。我想说通常最简单的方法是每条消息有一个处理程序,而不是像你一样将两条不同的消息发送到同一个处理程序,所以我选择了
pub.subscribe(self.on_status_result, 'prepare.running')
pub.subscribe(self.on_status_finished, 'prepare.complete')
您可以保留 on_status_result
不变并定义类似的 on_status_finished
。在我的例子中,我后来有一个启用新按钮的按钮,让你做一些真正的工作。
N.B. 在命名消息的有效载荷时需要小心 - pubsub
推断出相当多的关于它在那里期望的信息一开始它吸引了我。
P.S。就在准备这个答案的最后 - 我发现 this blog post. It says something similar to what I have above, so I won't reproduce it, but they use the other method of instantiating Publisher()
like your original example - implying that should work too. You may prefer the wording there. Simlarly - you might find this wxPython wiki 页面很有用。
这是 wxPython 的 Phoenix 分支。
为了不阻塞 GUI,我正在尝试 运行 几个线程。
我的两个线程工作正常,但另一个线程似乎从未达到其绑定结果函数。我可以看出它是 运行ning,它似乎没有正确 post 事件。
这是主要计算线程的结果函数:
def on_status_result(self, event):
if not self.panel.progress_bar.GetRange():
self.panel.progress_bar.SetRange(event.data.parcel_count)
self.panel.progress_bar.SetValue(event.data.current_parcel)
self.panel.status_label.SetLabel(event.data.message)
这是我绑定它们的方式:
from wx.lib.pubsub.core import Publisher
PUB = Publisher()
这是我绑定方法的方式:
def post_event(message, data):
wx.CallAfter(lambda *a: Publisher().sendMessage(message, data=data))
这是线程。第一个不起作用,但后两个起作用:
class PrepareThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
for status in prepare_collection(DATABASE, self._previous_id, self._current_id, self._year, self._col_type,
self._lock):
post_event('prepare.running', status)
post_event('prepare.complete', None)
return None
def abort(self):
self._want_abort = True
class SetupThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
do_more_stuff_with_the_database()
return None
def abort(self):
self._want_abort = True
class LatestCollectionsThread(threading.Thread):
def __init__(self, notify_window):
threading.Thread.__init__(self)
self._notify_window = notify_window
self._want_abort = False
def run(self):
while not self._want_abort:
do_stuff_with_my_database()
return None
def abort(self):
self._want_abort = True
prepare_collection
是一个生成 Status
对象的函数,如下所示:
class Status:
def __init__(self, parcel_count, current_parcel, total, message):
self.parcel_count = parcel_count
self.current_parcel = current_parcel
self.total = total
self.message = message
我是这样 creating/starting/subscribing PrepareThread 的:
MainForm(wx.Form):
prepare_thread = PrepareThread(self)
prepare_thread.start()
self.pub = Publisher()
self.pub.subscribe(self.on_status_result, 'prepare.running')
self.pub.subscribe(self.on_status_result, 'prepare.complete')
def on_status_result(self, event):
if not self.panel.progress_bar.GetRange():
self.panel.progress_bar.SetRange(event.data.parcel_count)
self.panel.progress_bar.SetValue(event.data.current_parcel)
self.panel.status_label.SetLabel(event.data.message)
我已经尝试用 range(10)
删除 prepare_collection
,但我仍然没有点击事件处理程序。
问题是事件系统最终会从线程本身调用更新函数(事件处理程序),你几乎不应该这样做(基本上你最终会遇到奇怪的竞争条件和工件)......总是在主线程中进行回调。
wxPython 已经考虑到这一点,任何用 wx.CallAfter 调用的方法都将从主程序循环中调用,主程序循环总是在主线程中 运行。这与 wx.pubsub 模块相结合,使您可以轻松创建自己的事件框架……像这样
def MyPostEvent(event_name,event_data):
#just a helper that triggers the event with wx.CallAfter
wx.CallAfter(lambda *a:Publisher().sendMessage(event_name,data=event_data))
#then to post an event
MyPostEvent("some_event.i_made_up",{"payload":True})
#then in your main thread subscribe
def OnEventHandler(evt):
print "EVT.data",evt.data
pub = Publisher()
pub.subscribe("some_event.i_made_up",OnEventHandler)
这可能是一个相当复杂的答案,要准确计算出代码的每个部分中有哪些片段(即它们都位于哪些文件中)有点困难。我假设您想保留执行此操作的 pubsub
方法(我认为这是个好主意)。如果您的真实项目的结构非常复杂,您可能需要比我在这里使用的更复杂的 Publisher
管理 - 让我知道...
开始 - 我会先剧透 - 这里有一个 one file solution 你似乎想要的东西 - 一个带有按钮的面板,用于启动准备线程、状态栏和准备时的处理程序完成的。在 Python 2.7 上使用 wxPython Phoenix 64 位 Python 3 和老式的 wxPython 进行了测试。两者都在 Windows - 但如有必要,我可以在 Linux 框中旋转它。
总结该文件的重要(非样板)部分
您需要一个单独的 Publisher
对象,您的线程将消息发送到该对象并且您的主线程(我猜在您的示例中为 MainForm
)订阅。您可以为每个线程管理一个 Publisher
,但我认为这里您只需要一个用于 PrepareThread
,所以我现在将使用该模型。
在文件的顶部,使用
from wx.lib.pubsub import pub
这让 pubsub
管理实例化单个 Publisher
对象。
在你的线程中,正如你所做的那样,在那里发布消息 - 对你的 post_event
助手的一个小修正:
def post_event(message, data):
wx.CallAfter(lambda *a: pub.sendMessage(message, data=data))
在您的主线程中 - 订阅这些消息。我想说通常最简单的方法是每条消息有一个处理程序,而不是像你一样将两条不同的消息发送到同一个处理程序,所以我选择了
pub.subscribe(self.on_status_result, 'prepare.running')
pub.subscribe(self.on_status_finished, 'prepare.complete')
您可以保留 on_status_result
不变并定义类似的 on_status_finished
。在我的例子中,我后来有一个启用新按钮的按钮,让你做一些真正的工作。
N.B. 在命名消息的有效载荷时需要小心 - pubsub
推断出相当多的关于它在那里期望的信息一开始它吸引了我。
P.S。就在准备这个答案的最后 - 我发现 this blog post. It says something similar to what I have above, so I won't reproduce it, but they use the other method of instantiating Publisher()
like your original example - implying that should work too. You may prefer the wording there. Simlarly - you might find this wxPython wiki 页面很有用。