长时间运行的shell脚本会生成stdout和stderr,我想在GUI中的textctrl上显示它。这可以使用线程并将GUI线程与shell脚本的线程分开。然而,当我实现多处理时,我遇到了障碍。这是我的下载代码:
#!/usr/bin/env python
import wx
import sys, subprocess
from multiprocessing import Process, Queue
from Queue import Empty
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.button = wx.Button(self, -1 , "Run")
self.output = wx.TextCtrl(self, -1, '', style=wx.TE_MULTILINE|\
wx.TE_READONLY|wx.HSCROLL)
self.Bind(wx.EVT_BUTTON, self.OnButton, self.button)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.output, -1, wx.EXPAND, 0)
sizer.Add(self.button)
self.SetSizerAndFit(sizer)
self.Centre()
def OnButton(self, event):
numtasks = 4 # total number of tasks to run
numprocs = 2 # number of processors = number of parallel tasks
work_queue = Queue()
for i in xrange(numtasks):
work_queue.put(i)
processes = [Process(target=self.doWork, args=(work_queue, ))
for i in range(numprocs)]
for p in processes:
p.daemon = True
p.start()
def doWork(self, work_queue):
while True:
try:
x = work_queue.get(block=False)
self.runScript(x)
except Empty:
print "Queue Empty"
break
def runScript(self, taskno):
print '## Now Running: ', taskno
command = ['./script.sh']
proc = subprocess.Popen(command, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
while True:
stdout = proc.stdout.readline()
if not stdout:
break
#sys.stdout.flush() #no need for flush, apparently it is embedded in the multiprocessing module
self.output.AppendText(stdout.rstrip()) #this is the part that doesn't work.
if __name__ == "__main__":
app = wx.App(0)
frame = MyFrame(None, title="shell2textctrl", size=(500,500))
frame.Show(True)
app.MainLoop()
我已经根据别人的建议尝试了很多解决方案。其中包括:使用wx.CallAfter或wx.lib.delayedresult使GUI响应;几个线程配方(例如,wxApplication Development Cookbook,threadpool,其他......)并让multiprocess()从一个单独的线程运行;重新定义sys.stdout.write以写入textctrl;等等。他们都没有成功。任何人都可以提供解决方案和工作代码吗?你可以使用这个脚本写在这里,我忘了谁:
#!/bin/tcsh -f
@ i = 1
while ($i <= 20)
echo $i
@ i += 1
sleep 0.2
end
答案 0 :(得分:1)
我不知道这是否会对你有所帮助,但我在这里使用子进程有一个这样的例子:
http://www.blog.pythonlibrary.org/2010/06/05/python-running-ping-traceroute-and-more/
请参阅pingIP和tracertIP方法。如果您的shell脚本是用Python编写的,那么您可以添加某种类型的生成器,就像我在该文章中使用的那些发布回GUI一样。您可能已经阅读过LongRunningProcess wiki文章,但我接受了这个并在此处制作了自己的教程:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
答案 1 :(得分:1)
我找到了一个在Roger Stuckey的Simpler wxPython Multiprocessing Example框架之上实现修改后的输出队列的解决方案。下面的代码甚至比他更简单;它也可以清理一下。希望它可以帮助别人。我仍然有一种唠叨的感觉,即应该有一种更直接的方式来做到这一点。
import getopt, math, random, sys, time, types, wx, subprocess
from multiprocessing import Process, Queue, cpu_count, current_process, freeze_support
from Queue import Empty
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, wx.Point(700, 500), wx.Size(300, 200))
self.panel = wx.Panel(self, wx.ID_ANY)
#widgets
self.start_bt = wx.Button(self.panel, wx.ID_ANY, "Start")
self.Bind(wx.EVT_BUTTON, self.OnButton, self.start_bt)
self.output_tc = wx.TextCtrl(self.panel, wx.ID_ANY, style=wx.TE_MULTILINE|wx.TE_READONLY)
# sizer
self.sizer = wx.GridBagSizer(5, 5)
self.sizer.Add(self.start_bt, (0, 0), flag=wx.ALIGN_CENTER|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
self.sizer.Add(self.output_tc, (1, 0), flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=5)
self.sizer.AddGrowableCol(0)
self.sizer.AddGrowableRow(1)
self.panel.SetSizer(self.sizer)
# Set some program flags
self.keepgoing = True
self.i = 0
self.j = 0
def OnButton(self, event):
self.start_bt.Enable(False)
self.numtasks = 4
self.numproc = 2
#self.numproc = cpu_count()
self.output_tc.AppendText('Number of processes = %d\n' % self.numproc)
# Create the queues
self.taskQueue = Queue()
self.outputQueue = Queue()
# Create the task list
self.Tasks = range(self.numtasks)
# The worker processes...
for n in range(self.numproc):
process = Process(target=self.worker, args=(self.taskQueue, self.outputQueue))
process.start()
# Start processing tasks
self.processTasks(self.update)
if (self.keepgoing):
self.start_bt.Enable(True)
def processTasks(self, resfunc=None):
self.keepgoing = True
# Submit first set of tasks
numprocstart = min(self.numproc, self.numtasks)
for self.i in range(numprocstart):
self.taskQueue.put(self.Tasks[self.i])
self.j = -1 # done queue index
self.i = numprocstart - 1 # task queue index
while (self.j < self.i):
# Get and print results
self.j += 1
output = None
while output != 'STOP!':
try:
output = self.outputQueue.get()
if output != 'STOP!':
resfunc(output)
except Empty:
break
if ((self.keepgoing) and (self.i + 1 < self.numtasks)):
# Submit another task
self.i += 1
self.taskQueue.put(self.Tasks[self.i])
def update(self, output):
self.output_tc.AppendText('%s PID=%d Task=%d : %s\n' % output)
wx.YieldIfNeeded()
def worker(self, inputq, outputq):
while True:
try:
tasknum = inputq.get()
print '## Now Running: ', tasknum #this goes to terminal/console. Add it to outputq if you'd like it on the TextCtrl.
command = ['./script.sh']
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
r = p.stdout.readline()
if not r:
outputq.put('STOP!')
break
outputq.put(( current_process().name, current_process().pid, tasknum, r.rstrip()))
except Empty:
break
# The worker must not require any existing object for execution!
worker = classmethod(worker)
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, -1, 'stdout to GUI using multiprocessing')
self.frame.Show(True)
self.frame.Center()
return True
if __name__ == '__main__':
freeze_support()
app = MyApp(0)
app.MainLoop()