2011-04-29 42 views
1

长时间运行的shell脚本会生成stdout和stderr,我想在GUI中的textctrl上显示stdout和stderr。这可以使用线程和从shell脚本的线程分离GUI线程。但是,当我实现多处理时,我遇到了障碍。这里是我的-stripped下调代码:如何在wxPython TextCtrl中捕获在独立进程中运行的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,线程池,其他...),并使多进程()从一个单独的线程运行;重新定义sys.stdout.write写入textctrl;和什么。他们都没有成功。任何人都可以提供一个解决方案和工作代码?你可以使用这个脚本写在这里的某个地方被我忘了谁:

#!/bin/tcsh -f 
@ i = 1 
while ($i <= 20) 
echo $i 
@ i += 1 
sleep 0.2 
end 

回答

1

我发现它实现了罗杰 - 斯塔基的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() 
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/

+0

事实上,我根本看LongRunningProcess文章,你的教程(非常有用!)。我可以让我的代码与_threading_一起工作,但不能与_multiprocessing_一起使用。这很愚蠢,因为多处理基于线程。然后再次理解这两个模块如何工作是非常有限的。不幸的是,我真的需要使用多处理。 – 2011-04-30 04:14:39

+0

好的。我还没有使用过多处理,所以目前我无法对此进行评论。如果我找到一些时间,我会深入研究。 – 2011-04-30 14:01:33

相关问题