wxPython:线程问题

时间:2011-06-09 09:34:58

标签: multithreading wxpython

我的计划不断扩展。

在MainPanel类的MainProgram方法中,我正在使用其他程序中的一些函数。在使用它时,GUI会一直挂起,直到完成,我想通过为此方法使用新线程来解决此问题。

执行此操作时,执行OnRun时出错。它说:

  

Unhandled exception in thread started by <bound method MainPanel.OnIndex of <__main__.MainPanel; proxy of <Swig Object of type 'wxPanel *' at 0x526e238> >>

它认为这与onIndex设置som值与self.textOutput有关。现在,我该如何解决我的这个小问题?

非常感谢帮助! =)

import wx, thread 

ID_EXIT = 110

class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        self.buttonRun = wx.Button(self, label="Run")
        self.buttonRun.Bind(wx.EVT_BUTTON, self.OnRun )
        self.buttonExit = wx.Button(self, label="Exit")
        self.buttonExit.Bind(wx.EVT_BUTTON, self.OnExit)

        self.labelChooseRoot = wx.StaticText(self, label ="Root catalog: ") 
        self.labelScratchWrk = wx.StaticText(self, label ="Scratch workspace: ")
        self.labelMergeFile = wx.StaticText(self, label ="Merge file: ")

        self.textChooseRoot = wx.TextCtrl(self, size=(210, -1))
        self.textChooseRoot.Bind(wx.EVT_LEFT_UP, self.OnChooseRoot)
        self.textScratchWrk = wx.TextCtrl(self, size=(210, -1))
        self.textMergeFile = wx.TextCtrl(self, size=(210, -1))
        self.textOutput = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)

        self.sizerF = wx.FlexGridSizer(3, 2, 5, 5)
        self.sizerF.Add(self.labelChooseRoot)  #row 1, col 1
        self.sizerF.Add(self.textChooseRoot)   #row 1, col 2
        self.sizerF.Add(self.labelScratchWrk)  #row 2, col 1
        self.sizerF.Add(self.textScratchWrk)   #row 2, col 2
        self.sizerF.Add(self.labelMergeFile)   #row 3, col 1
        self.sizerF.Add(self.textMergeFile)    #row 3, col 2

        self.sizerB = wx.BoxSizer(wx.VERTICAL)
        self.sizerB.Add(self.buttonRun, 1, wx.ALIGN_RIGHT|wx.ALL, 5)
        self.sizerB.Add(self.buttonExit, 0, wx.ALIGN_RIGHT|wx.ALL, 5)

        self.sizer1 = wx.BoxSizer()
        self.sizer1.Add(self.sizerF, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL, 10)
        self.sizer1.Add(self.sizerB, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)

        self.sizer2 = wx.BoxSizer()
        self.sizer2.Add(self.textOutput, 1, wx.EXPAND | wx.ALL, 5)

        self.sizerFinal = wx.BoxSizer(wx.VERTICAL)
        self.sizerFinal.Add(self.sizer1, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
        self.sizerFinal.Add(self.sizer2, 1, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)

        self.SetSizerAndFit(self.sizerFinal)


    def OnChooseRoot(self, event):
        dlg = wx.DirDialog(self, "Choose a directory:", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            root_path = dlg.GetPath()
            self.textChooseRoot.SetValue(root_path)
        dlg.Destroy()

    def OnRun(self, event):
        #Check first if input values are
        thread.start_new_thread(self.OnIndex, ())

    def OnIndex(self):
        #Do something and post to self.textOutput what you do.

    def OnExit(self, event):
        self.GetParent().Close()


class MainWindow(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="IndexGenerator", size=(430, 330), 
                          style=((wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | 
                                  wx.STAY_ON_TOP) ^ wx.RESIZE_BORDER))
        self.CreateStatusBar() 

        self.fileMenu = wx.Menu()
        self.fileMenu.Append(ID_EXIT, "E&xit", "Exit the program")
        self.menuBar = wx.MenuBar()
        self.menuBar.Append(self.fileMenu, "&File")
        self.SetMenuBar(self.menuBar)
        wx.EVT_MENU(self, ID_EXIT, self.OnExit)                    

        self.Panel = MainPanel(self)

        self.CentreOnScreen()
        self.Show()

    def OnExit(self,  event):
        self.Close()

if __name__ == "__main__":
    app = wx.App(False)
    frame = MainWindow()
    app.MainLoop()

[编辑:]这是OnRun和OnIndex方法的摘录。有没有()到很多或者是,?

    def OnRun(self, Event=None):
        #---Check input values, continue if not wrong
        if self.CheckValid() == 0:
            #Get root directory
            root_path = self.textChooseRoot.GetValue()
            #Get scratch workspace
            scratch_workspace =self.textScratchWrk.GetValue()
            #Get merge file
            merge_fil = self.textMergeFile.GetValue()

            thread.start_new_thread(self.OnIndex, (root_path,scratch_workspace,merge_fil))

    def showmsg(self, msg):
        self.textOutput.AppendText(msg + "\n")


    def OnIndex(self,root_path,scratch_workspace,merge_fil):
            #---PUBSUB - publishes a message to the "show.statusbar"
##            msg = "Please wait...Program is running..."
##            Publisher().sendMessage(("show.statusbar"), msg)
            #---START INDEX GENERATOR CODE
            gp.overwriteoutput = 1
            gp.OutputMFlag = "DISABLED"
            gp.OutputZFlag = "DISABLED"
            fc_List = {}


            #Get log file. For now a constant. Needs to be changed.
            logfil = open("C:\\Python26\\Code\\log.txt", mode = "w")

            fold_nr = 0
            for root_fold, dirs, files in os.walk(root_path):
                root_fold_low = root_fold.lower()
                if not root_fold_low.find(".gdb") > -1:
                    fold_nr += 1
                    tot_t = time.clock()

                    wx.CallAfter(self.textOutput.AppendText, ("Mappe : " + str(fold_nr) + " : " + root_fold + "\n"))

3 个答案:

答案 0 :(得分:2)

与wx对象的所有交互都应该在主线程中。

一个简单的解决方法是使用wx.CallAfter(self.textOutput.SetValue, "output")而不是self.textOutput.SetValue("output")

wx.CallAfter发送到main even循环后,一旦它到达它就会执行什么,因为主循环在主线程中,一切正常。

更新:合并代码片段:

import wx, thread, os, time

ID_EXIT = 110

class Dummy:
    pass

gp = Dummy()

class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        self.buttonRun = wx.Button(self, label="Run")
        self.buttonRun.Bind(wx.EVT_BUTTON, self.OnRun )
        self.buttonExit = wx.Button(self, label="Exit")
        self.buttonExit.Bind(wx.EVT_BUTTON, self.OnExit)

        self.labelChooseRoot = wx.StaticText(self, label ="Root catalog: ") 
        self.labelScratchWrk = wx.StaticText(self, label ="Scratch workspace: ")
        self.labelMergeFile = wx.StaticText(self, label ="Merge file: ")

        self.textChooseRoot = wx.TextCtrl(self, size=(210, -1))
        self.textChooseRoot.Bind(wx.EVT_LEFT_UP, self.OnChooseRoot)
        self.textScratchWrk = wx.TextCtrl(self, size=(210, -1))
        self.textMergeFile = wx.TextCtrl(self, size=(210, -1))
        self.textOutput = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)

        self.sizerF = wx.FlexGridSizer(3, 2, 5, 5)
        self.sizerF.Add(self.labelChooseRoot)  #row 1, col 1
        self.sizerF.Add(self.textChooseRoot)   #row 1, col 2
        self.sizerF.Add(self.labelScratchWrk)  #row 2, col 1
        self.sizerF.Add(self.textScratchWrk)   #row 2, col 2
        self.sizerF.Add(self.labelMergeFile)   #row 3, col 1
        self.sizerF.Add(self.textMergeFile)    #row 3, col 2

        self.sizerB = wx.BoxSizer(wx.VERTICAL)
        self.sizerB.Add(self.buttonRun, 1, wx.ALIGN_RIGHT|wx.ALL, 5)
        self.sizerB.Add(self.buttonExit, 0, wx.ALIGN_RIGHT|wx.ALL, 5)

        self.sizer1 = wx.BoxSizer()
        self.sizer1.Add(self.sizerF, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL, 10)
        self.sizer1.Add(self.sizerB, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)

        self.sizer2 = wx.BoxSizer()
        self.sizer2.Add(self.textOutput, 1, wx.EXPAND | wx.ALL, 5)

        self.sizerFinal = wx.BoxSizer(wx.VERTICAL)
        self.sizerFinal.Add(self.sizer1, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
        self.sizerFinal.Add(self.sizer2, 1, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)

        self.SetSizerAndFit(self.sizerFinal)


    def OnChooseRoot(self, event):
        dlg = wx.DirDialog(self, "Choose a directory:", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            root_path = dlg.GetPath()
            self.textChooseRoot.SetValue(root_path)
        dlg.Destroy()

    def CheckValid(self):
        return 0

    def OnRun(self, Event=None):
        #---Check input values, continue if not wrong
        if self.CheckValid() == 0:
            #Get root directory
            root_path = self.textChooseRoot.GetValue()
            #Get scratch workspace
            scratch_workspace =self.textScratchWrk.GetValue()
            #Get merge file
            merge_fil = self.textMergeFile.GetValue()

            thread.start_new_thread(self.OnIndex, (root_path,scratch_workspace,merge_fil))

    def showmsg(self, msg):
        self.textOutput.AppendText(msg + "\n")

    def OnIndex(self,root_path,scratch_workspace,merge_fil):
            #---PUBSUB - publishes a message to the "show.statusbar"
##            msg = "Please wait...Program is running..."
##            Publisher().sendMessage(("show.statusbar"), msg)
            #---START INDEX GENERATOR CODE
            gp.overwriteoutput = 1
            gp.OutputMFlag = "DISABLED"
            gp.OutputZFlag = "DISABLED"
            fc_List = {}


            #Get log file. For now a constant. Needs to be changed.
            #logfil = open("C:\\Python26\\Code\\log.txt", mode = "w")

            fold_nr = 0
            for root_fold, dirs, files in os.walk(root_path):
                root_fold_low = root_fold.lower()
                if not root_fold_low.find(".gdb") > -1:
                    fold_nr += 1
                    tot_t = time.clock()

                    wx.CallAfter(self.textOutput.AppendText, ("Mappe : " + str(fold_nr) + " : " + root_fold + "\n"))

    def OnExit(self, event):
        self.GetParent().Close()


class MainWindow(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="IndexGenerator", size=(430, 330), 
                          style=((wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | 
                                  wx.STAY_ON_TOP) ^ wx.RESIZE_BORDER))
        self.CreateStatusBar() 

        self.fileMenu = wx.Menu()
        self.fileMenu.Append(ID_EXIT, "E&xit", "Exit the program")
        self.menuBar = wx.MenuBar()
        self.menuBar.Append(self.fileMenu, "&File")
        self.SetMenuBar(self.menuBar)
        wx.EVT_MENU(self, ID_EXIT, self.OnExit)                    

        self.Panel = MainPanel(self)

        self.CentreOnScreen()
        self.Show()

    def OnExit(self,  event):
        self.Close()

if __name__ == "__main__":
    app = wx.App(False)
    frame = MainWindow()
    app.MainLoop()

答案 1 :(得分:1)

您只需使用线程安全方法将信息发送回主线程。正如其他人所说,你不能直接与主线程交互,否则会发生奇怪的事情。这是一篇关于你可以通过各种方式完成这一壮举的非常好的文章:

http://wiki.wxpython.org/LongRunningTasks

这是关于这个主题的另一篇文章:

http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/

答案 2 :(得分:0)

您无法与其他线程的GUI进行交互。你的回调应该是这样的:

def OnRun(self, event):
    <get any data you need from the GUI>
    thread.start_new_thread(self.WorkerThread, (parameter1, parameter2, ...))

def WorkerThread(self, parameter1, parameter2, ...):
    # do time-consuming work here. To send data to
    # the GUI use CallAfter:
    wx.CallAfter(self.textOutput.AppendText, "whatever") 

关键是在生成工作线程之前,在主线程中进行所有非耗时的GUI交互。一旦工作线程启动,它应该拥有它需要的所有数据。然后,它可以继续完成其工作,并使用CallAfter与主线程进行通信。