#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: Я не могу понять, что вы сделали не так. Я не вижу ваш код и не вижу ваших ошибок.