阻止

时间:2015-09-08 20:53:55

标签: python multithreading wxpython

这是在wxPython的Phoenix分支中。

为了不阻止GUI,我试图运行一些线程。

我的两个线程工作正常,但另一个线程似乎永远不会达到其绑定结果函数。我可以说它正在运行,它似乎没有正确发布事件。

这里是主计算线程的结果函数:

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

以下是我创建/启动/订阅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)

我已尝试使用prepare_collection删除range(10),但我仍然无法点击事件处理程序。

2 个答案:

答案 0 :(得分:4)

问题是事件系统最终从线程本身调用更新函数(事件处理程序),你几乎不应该这样做(基本上你最终会遇到奇怪的竞争条件和工件)...总是让主线程中的回调。

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)

答案 1 :(得分:4)

这可能是一个非常复杂的答案,而且您在代码的每个部分中确切地知道了哪些代码段(即它们都存在于哪些文件中)有点困难。我假设您要保留pubsub方法(我认为这是一个好主意)。如果您的真实项目的结构非常复杂,您可能需要比我在这里使用的Publisher更复杂的管理 - 请告诉我......

接下来 - 我会把扰流板放在第一位 - 这里' sa one file solution用于你想要的东西 - 一个带有按钮的面板,用于启动准备线程,状态栏和处理程序准备工作完成后。使用wxPython Phoenix 64位Python 3和老式的wxPython在Python 2.7上进行测试。两者都在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。在我的例子中,我稍后使用了一个新按钮来让你做一些真正的工作。

NB 在命名消息的有效负载时需要小心 - pubsub可以推断出相关内容的相关信息,它首先引起了我的注意

P.S。在准备这个答案的最后 - 我找到了this blog post。它说的与我上面的相似,所以我不会重现它,但是他们使用另一种方法来实例化Publisher(),就像你原来的例子一样 - 暗示它也应该有效。您可能更喜欢那里的措辞。同样 - 您可能会发现this wxPython wiki页有用。