在wxPython中使用多线程
我正在写一个wxpython程序。如果你在程序中打开一个文件,它会计算文件中的行数,并把这个数字显示在一个叫做staticText
的控件里。
下面是相关的代码:
在class Frame
里
def on_select_file(self, event):
'''Event handler for selecting file'''
filepath = getFile() # This is a seperate method which asks the user to select a file and returns the path
threading.Thread(target = methods.count_lines, args = (filepath, self.staticText)).start()
methods.py
def count_lines(filepath, staticText):
with open(filepath) as f:
for i, _ in enumerate(f): pass
staticText.SetLabel(str(i+1))
我处理的大部分文件都非常大(3-4 GB),大约有2500万行。所以如果我打开一个大文件,计算和显示行数需要花费几十秒的时间。不过,如果我选择一个大文件,然后在staticText
控件更新之前再打开一个较小的文件,staticText
控件会显示新文件的行数,但过了一段时间后又会显示之前那个大文件的行数。我明白这是因为之前的计算线程还在运行,等它结束后才更新了控件。
我尝试通过传递一个标志变量作为参数给计数函数,来检查控件是否已经更新。但这似乎没有效果。还有其他办法可以避免这个问题吗?
2 个回答
3
简单来说,你需要创建一个自定义事件类型,这个事件里包含当前的行数。然后在你的工作线程中,定期使用 wx.PostEvent() 向包含静态文本控件的类发送这个事件。等到主线程恢复并处理事件循环时,你就可以用接收到的事件里的行数来设置文本内容。
像这样应该可以工作:
import time
from threading import *
import wx
import os.path
EVT_LINE_NUMBER_UPDATE_ID = wx.NewId()
class LineNumberUpdateEvent(wx.PyEvent):
def __init__(self, lineNum):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_LINE_NUMBER_UPDATE_ID)
self.lineNumber = lineNum
class WorkerThread(Thread):
def __init__(self, notify_window, filename):
Thread.__init__(self)
self._notify_window = notify_window
self._filename = filename
self.start()
def run(self):
with open(this._filename,"r") as file:
count = 0;
for line in file:
count++
if count % 50 == 0: # post an event every 50 lines
wx.PostEvent(self._notify_window, LineNumberUpdateEvent(count))
wx.PostEvent(self._notify_window, LineNumberUpdateEvent(count)) # last event
class MainFrame(wx.Frame):
def __init__(self, parent, id, filename):
wx.Frame.__init__(self, parent, id, 'Threaded File Loader')
self.status = wx.StaticText(self, -1, '', pos=(0,100))
self.Bind(EVT_LINE_NUMBER_UPDATE_ID, self.OnLineNumberUpdate)
if (os.path.isfile(filename))
self.worker = WorkerThread(self,filename)
def OnLineNumberUpdate(self, event):
self.status.SetLabel(str(event.lineNumber))
这个内容是从 wx Wiki 上的一个例子改编而来的:
0
我对Sir Digby Chicken Caesa的回答中的代码进行了修改,以满足我的需求。与其创建一个自定义事件并在一段时间后触发它,我设置了一个行计数线程,这样每当发生文件打开事件时,如果之前的线程还在运行,就可以中止这个线程。
下面是一个简单的实现示例:
import threading.Thread
class Line_Counter(threading.Thread):
def __init__(self, staticText, filename):
threading.Thread.__init__(self)
self.staticText = staticText
def run(self):
self.exit = False
with open(self.filename) as f:
if self.exit:
return
for count, _ in enumerate(f): pass
self.staticText.SetLabel(str(count + 1))
def abort(self):
self.exit = True
class MainFrame(wx.Frame):
def __init__(self, parent, id, filename):
wx.Frame.__init__(self, parent, id, 'Threaded File Loader')
self.line_counter = False # Set up a dummy thread variable
#### remaining code
self.file_open_button.Bind( wx.EVT_BUTTON, self.OnOpenFile ) # Event handler for opening file
def OnOpenFile(self, event):
filepath = getFile() # Creates a file open dialog and returns the file path
if self.line_counter: # Checks if a counter thread is already running
self.line_counter.abort() # Stops the thread if True
self.line_counter = Line_Counter(self.staticText, filename).start() # Starts a new thread