wxPython: Problems with thread

天大地大妈咪最大 提交于 2020-01-15 11:26:06

问题


My program keeps expanding.

In my MainProgram method in the MainPanel class I'm using some functions from an other program. While using this the GUI hangs until that is finished and I want to solve this by using a new thread for this method.

While doing this I get an error when executing OnRun. It says:

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

It think this has something to do with the OnIndex setting som values to self.textOutput. Now, how can I solve this little problem of mine?

Help is much appreciated! =)

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()

[EDIT:] Here is an extract of the OnRun and OnIndex methods. Is there a () to much or maybe an , ?

    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"))

回答1:


All interaction with wx object should be in the main thread.

An easy fix would be to use something like wx.CallAfter(self.textOutput.SetValue, "output") instead of self.textOutput.SetValue("output").

wx.CallAfter sends to the main even loop what to execute as soon as it gets around to it and since the main loop is in the main thread everything works out fine.

UPDATE: Working merged code snippets:

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()



回答2:


You just need to use a threadsafe method to send information back to the main thread. As the others have stated, you cannot interact with the main thread directly or weird things will happen. Here's a really good article on the various ways you can accomplish this feat:

http://wiki.wxpython.org/LongRunningTasks

And here's another article on the subject:

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




回答3:


You cannot interact with the GUI from another thread. Your callback should work like this:

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") 

The key is to do all the non-time-consuming GUI interaction in the main thread before spawning the worker thread. Once the worker thread starts it should have all the data it needs. It can then proceed to do its work, and use CallAfter to communicate back to the main thread.



来源:https://stackoverflow.com/questions/6290791/wxpython-problems-with-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!