【发布时间】:2015-04-23 10:50:43
【问题描述】:
我正在使用 wxpython 构建前端 GUI,用于分析和处理音频文件的命令行工具。文件被加载到 GUI 中;然后启动执行分析和调整步骤的线程;最后,这些过程的结果显示在主窗口中。
我一直在努力编写线程安全的代码;但是,某些线程仍然无法完成(应该注意的是,当我第二次手动启动它们时,它们通常会运行到完成)。下面我包含了我的程序的精简版,其中包含 AnalysisThread、AdjustThread 和 MainWindow 的类。主窗口中的按钮绑定到函数“OnAnalyze”和“OnAdjust”,它们创建相应线程类的实例。线程本身通过 wx.CallAfter 和 Publisher 与 GUI 通信。据我了解,这应该允许数据在主进程和线程之间安全地来回传递。如果有人能指出我在下面的代码中出错的地方,我将不胜感激。
如果我无法解决线程安全问题,我的备用计划是以某种方式检测线程的死亡并尝试在后台“恢复”它,而用户不知道存在故障。这看起来合理吗?如果是这样,我们将非常欢迎您就如何实现这一点提出建议。
非常感谢。
#!/usr/bin/python
import wx
import time
from threading import Thread
import os, sys, re, subprocess, shutil
from wx.lib.pubsub import setuparg1
from wx.lib.pubsub import pub as Publisher
#Start a thread that analyzes audio files.
class AnalysisThread(Thread):
def __init__(self,args):
Thread.__init__(self)
self.file = args[0]
self.index = args[1]
self.setDaemon(True)
self.start()
def run(self):
proc = subprocess.Popen(['ffmpeg', '-nostats', '-i', self.file, '-filter_complex', 'ebur128=peak=true+sample', '-f', 'null', '-'], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
flag = 0
summary = ""
while proc.poll() is None:
line = proc.stdout.readline()
if line:
endProcess = re.search(r'Summary', line)
if endProcess is not None:
flag = 1
if flag:
summary += line
wx.CallAfter(Publisher.sendMessage, "update", (self.file, summary, self.index))
#Start a thread that adjusts audio files so that they conform to EBU loudness standards.
class AdjustThread(Thread):
def __init__(self,args):
Thread.__init__(self)
self.file = args[0]
self.index = args[1]
self.IL = args[2]
self.TP = args[3]
self.SP = args[4]
self.setDaemon(True)
self.start()
def run(self):
proc = subprocess.Popen(['ffmpeg', '-nostats', '-i', adjusted_file, '-filter_complex', 'ebur128=peak=true+sample', '-f', 'null', '-'], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
flag = 0
summary = ""
while proc.poll() is None:
line = proc.stdout.readline()
if line:
endProcess = re.search(r'Summary', line)
if endProcess is not None:
flag = 1
if flag:
summary += line
wx.CallAfter(Publisher.sendMessage, "update", (self.file, summary, self.index))
class MainWindow(wx.Frame):
fileList = collections.OrderedDict()
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(900, 400))
Publisher.subscribe(self.UpdateDisplay, "update")
#Add "analyze" and "Adjust" buttons to the main frame.
panel = wx.Panel(self, -1)
vbox = wx.BoxSizer(wx.VERTICAL)
self.ana = wx.Button(panel, -1, 'Analyze', size=(100, -1))
self.adj = wx.Button(panel, -1, 'Adjust', size=(100, -1))
self.Bind(wx.EVT_BUTTON, self.OnAnalyze, id=self.ana.GetId())
self.Bind(wx.EVT_BUTTON, self.OnAdjust, id=self.adj.GetId())
vbox.Add(self.ana, 0, wx.ALL, 10)
vbox.Add(self.adj, 0, wx.ALL, 10)
vbox.Add(self.list, 1, wx.EXPAND | wx.TOP, 3)
vbox.Add((-1, 10))
panel.SetSizer(hbox)
self.Centre()
self.Show(True)
#This function gets called when "Analyze" is pressed.
def OnAnalyze(self, event):
for (file,index) in toAnalyze:
#Add a progess bar
item = self.list.GetItem(index,2)
gauge = item.GetWindow()
gauge.Pulse()
#Launch the analysis thread
AnalysisThread(args=(file,index,))
#This function gets called when "Adjust" is pressed.
def OnAdjust(self, event):
for (file,index) in toAdjust:
gauge = wx.Gauge(self.list,-1,range=50,size=(width,15),style=wx.GA_HORIZONTAL | wx.GA_SMOOTH)
gauge.Pulse() #shouldn't start this right away...
item.SetWindow(gauge, wx.ALIGN_CENTRE)
self.list.SetItem(item)
#Launch the adjust thread
AdjustThread(args=(file,index,intloud,truepeak,samplepeak))
#This function is invoked by the Publisher.
def UpdateDisplay(self, msg):
t = msg.data
file = t[0]
summary = t[1]
i = t[2]
self.ProcessSummary(file, summary, i)
item = self.list.GetItem(i,2)
gauge = item.GetWindow()
gauge.SetValue(50)
self.fileList[file][1] = True
#Display information from the threads in the main frame.
def ProcessSummary(self, file, summary, i):
loudnessRange = re.search(r'LRA:\s(.+?) LU', summary)
if loudnessRange is not None:
LRA = loudnessRange.group(1)
else:
LRA = "n/a"
self.list.SetStringItem(i,7,LRA)
self.fileList[file][6] = LRA
intloud = re.search(r'I:\s(.+?) LUFS', summary)
if intloud is not None:
IL = intloud.group(1)
else:
IL = "n/a"
self.list.SetStringItem(i,4,IL)
self.fileList[file][3] = IL
truePeak = re.search(r'True peak:\s+Peak:\s(.+?) dBFS', summary)
if truePeak is not None:
TP = truePeak.group(1)
else:
TP = "n/a"
self.list.SetStringItem(i,5,TP)
self.fileList[file][4] = TP
samplePeak = re.search(r'Sample peak:\s+Peak:\s(.+?) dBFS', summary)
if samplePeak is not None:
SP = samplePeak.group(1)
else:
SP = "n/a"
self.list.SetStringItem(i,6,SP)
self.fileList[file][5] = SP
app = wx.App()
MainWindow(None, -1, 'Leveler')
app.MainLoop()
【问题讨论】:
-
你看过这个答案了吗? *.com/a/18495032/566035 我个人也使用wxpython.org/docs/api/wx.lib.delayedresult-module.html 做了类似的事情。延迟结果也不错。
-
嗨 otterb,我确实看到了堆栈溢出帖子,并且实际上尝试将其中一些解决方案集成到我自己的代码中......但非常感谢您提供指向延迟结果模块的链接!我一定会检查出来,让你知道它是怎么回事。只是为了我自己的安心:我是否正确地假设线程的间歇性和任意性故障 不是 wxPython GUI 的一个不可避免的属性?我正在构建一个将交付给相当多用户的产品,因此稳健性至关重要。谢谢!
-
至少根据我自己的经验,delayedresult 效果很好并且很容易实现(对我来说)。我相信 python 和 wxpython(我使用的是 2.8 而不是 3)都非常成熟,所以它应该是稳定的。 wxpython 3 相对较新(我猜还是测试版),我没有太多经验。
-
再次感谢!
-
您好 otterb:您能提供延迟结果模块的示例用法吗?我已经得到了类似“startWorker(self.OnAnalyze,self.AnalysisWorker,wargs =(file,))”的东西,其中self.OnAnalyze是当前函数,self.AnalysisWorker是调用线程的函数,给定“文件”参数。我已将 self.AnalysisWorker 简化为单个打印语句,并且它似乎在无限循环。我是否指定了错误的消费者?线程实际上在哪里被调用,数据以什么形式返回到 GUI?谢谢!
标签: python thread-safety wxpython python-multithreading