wxPython: проблемы с потоком

#multithreading #wxpython

#многопоточность #wxpython ( текст ) #wxpython

Вопрос:

Моя программа продолжает расширяться.

В моем методе MainProgram в классе mainPanel я использую некоторые функции из другой программы. При использовании этого графический интерфейс зависает, пока это не будет завершено, и я хочу решить это, используя новый поток для этого метода.

При выполнении этого я получаю ошибку при выполнении 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, "Eamp;xit", "Exit the program")
        self.menuBar = wx.MenuBar()
        self.menuBar.Append(self.fileMenu, "amp;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"))
  

Ответ №1:

Все взаимодействие с объектом wx должно осуществляться в основном потоке.

Простым решением было бы использовать что-то вроде wx.CallAfter(self.textOutput.SetValue, "output") вместо self.textOutput.SetValue("output") .

wx.CallAfter отправляет в основной четный цикл то, что нужно выполнить, как только дойдет до этого, и поскольку основной цикл находится в главном потоке, все работает нормально.

ОБНОВЛЕНИЕ: рабочие объединенные фрагменты кода:

 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, "Eamp;xit", "Exit the program")
        self.menuBar = wx.MenuBar()
        self.menuBar.Append(self.fileMenu, "amp;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. Здравствуйте. Спасибо, но я столкнулся с другой проблемой. У меня это тоже есть в методе: root_path = self.textChooseRoot. GetValue() Как мне использовать это с wx.CallAfter? Нет аргументов для передачи….

2. попробовал root_path = wx.CallAfter(self.textChooseRoot. GetValue(), ()) но это не сработало. В любом случае, я думаю, ваш ответ зависит от правильного получения root_path..

3. Я бы передал root_path в качестве аргумента OnIndex

4. Пытался это сделать. Все еще не работает. Похоже, что есть проблема с wx.CallAfter в OnIndex. используя это: wx.CallAfter(self.textOutput. Текст приложения, («Каталог :» str(cat_nr) «n»)) . Это должно сработать правильно? Мне кажется, то, что говорит Брайан, правильно.

5. Это должно быть, и это действительно работает. Что касается root_path, позаботьтесь о том, чтобы вы должны были использовать кортеж при предоставлении аргументов для целевого потока, например, так: thread.start_new_thread(self. OnIndex, (root_path,)) и вы определяете OnIndex как: def OnIndex(self, root_path)

Ответ №2:

Вам просто нужно использовать потокобезопасный метод для отправки информации обратно в основной поток. Как указывали другие, вы не можете взаимодействовать с основным потоком напрямую, иначе произойдут странные вещи. Вот действительно хорошая статья о различных способах достижения этой цели:

http://wiki.wxpython.org/LongRunningTasks

И вот еще одна статья на эту тему:

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

Ответ №3:

Вы не можете взаимодействовать с графическим интерфейсом из другого потока. Ваш обратный вызов должен работать следующим образом:

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

Ключ в том, чтобы выполнить все не отнимающие много времени взаимодействия с графическим интерфейсом в главном потоке перед созданием рабочего потока. После запуска рабочего потока у него должны быть все необходимые данные. Затем он может продолжить выполнять свою работу и использовать CallAfter для обратной связи с основным потоком.

Комментарии:

1. Привет, Брайан. Итак, передача того, что происходит с выводом текста из другой программы, тогда не вариант? Либо у меня все так, как есть, из-за чего графический интерфейс зависает во время выполнения этой другой программы, но при этом обновляется вывод текста. ИЛИ я могу сделать так, чтобы графический интерфейс не зависал, но терял возможность передавать что-либо в textOutput из другой программы? Я правильно это понимаю?

2. @sekstiseks: Нет, вы неправильно это понимаете. Мой пример показывает, что ваш рабочий поток может обновлять графический интерфейс — он просто делает это через CallAfter , а не вызывает какой-либо метод виджета напрямую.

3. Здравствуйте. Добавлена выдержка из методов OnIndex и OnRun, описанных выше. Что я делаю не так? Все работает нормально, если я удалю thread.start_new и wx.CallAfter после вызова .

4. @sekstiseks: Я не могу понять, что вы сделали не так. Я не вижу ваш код и не вижу ваших ошибок.