如何运行子流程,在GUI中显示其输出并允许它终止?

时间:2009-09-16 14:35:14

标签: python

我一直在尝试编写一个运行子进程的应用程序,并且(除其他外)在GUI中显示它们的输出,并允许用户单击按钮取消它们。我开始这样的过程:

queue = Queue.Queue(500)
process = subprocess.Popen(
    command,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT)
iothread = threading.Thread(
    target=simple_io_thread,
    args=(process.stdout, queue))
iothread.daemon=True
iothread.start()

其中simple_io_thread的定义如下:

def simple_io_thread(pipe, queue):
    while True:
        line = pipe.readline()
        queue.put(line, block=True)
        if line=="":
            break

这很好用。在我的UI中,我定期从队列中执行非阻塞“get”。但是,当我想终止子进程时,我的问题就来了。 (子进程是一个任意的进程,而不是我自己编写的。)我可以使用terminate方法来终止进程,但我不知道如何保证我的I / O线程将终止。它通常会在管道上进行阻塞I / O.这可能会或可能不会在我终止流程后的某个时间结束。 (如果子进程产生了另一个子进程,我可以终止第一个子进程,但是第二个进程仍然会保持管道打开。我甚至不确定如何让这些曾经的子进程彻底终止。)之后,I / O线程将尝试将输出排队,但我不想承诺无限期地从队列中读取。

理想情况下,我想要一些方法来请求终止子进程,阻塞一小段(<0.5s)的时间,之后保证I / O线程已退出(或将及时退出)不干涉任何其他事情)并且我可以停止从队列中读取。

对我来说,解决方案使用I / O线程并不重要。如果有另一种方法可以在Windows和Linux上使用Python 2.6和Tkinter GUI,那就没问题。


编辑 - Will的回答以及我在网上看到的其他语言中的其他事情表明操作系统希望你只关闭主线程上的文件句柄然后I / O线程应该出来它的阻塞读取。但是,正如我在评论中所描述的那样,这似乎对我不起作用。如果我在主线程上执行此操作:

process.stdout.close()

我明白了:

IOError: close() called during concurrent operation on the same file object.

...在主线程上。如果我在主线程上执行此操作:

os.close(process.stdout.fileno())

我明白了:

close failed in file object destructor: IOError: [Errno 9] Bad file descriptor

...稍后在主线程中尝试关闭文件句柄时。

4 个答案:

答案 0 :(得分:2)

我知道这是一个旧帖子,但是如果它仍然可以帮助任何人,我认为你的问题可以通过将subprocess.Popen实例传递给io_thread而不是它的输出流来解决。 如果您这样做,那么您可以将while True:行替换为while process.poll() == None:

process.poll()检查子进程返回码;如果该过程尚未完成,则没有一个(即process.poll() == None)。然后,您可以取消if line == "": break

我在这里的原因是因为我今天写了一个非常相似的剧本,我得到了: - IOError: close() called during concurrent operation on the same file object.个错误。

再次,如果它有所帮助,我认为我的问题源于(我的)io_thread做了一些过于高效的垃圾收集,并关闭了我给它的文件句柄(我可能错了,但它现在有效..)我的不同之处在于它不是守护进程,它通过subprocess.stdout迭代,而不是使用while循环..即: -

def io_thread(subprocess,logfile,lock):
    for line in subprocess.stdout:
        lock.acquire()
        print line,
        lock.release()
        logfile.write( line )

我还应该提一下,我将bufsize参数传递给subprocess.Popen,以便它是行缓冲的。

答案 1 :(得分:2)

这可能已经足够了,但对来自搜索引擎的人来说仍然有用......

它显示消息的原因是在子进程完成后它会关闭文件描述符,因此,守护进程线程(并发运行)将尝试使用那些关闭的描述符来引发错误。

通过在子进程wait()或communic()之前加入线程,方法应该足以抑制错误。

my_thread.join()
print my_thread.is_alive()
my_popen.communicate()

答案 2 :(得分:1)

在终止进程的代码中,您还可以显式os.close()线程正在读取的管道?

答案 3 :(得分:1)

你应该关闭写管道......但是当你编写代码时,你无法访问它。要做到这一点,你应该

  1. 装箱管道
  2. 将写入管道文件ID传递给Popen的{​​{1}}
  3. 使用读取管道文件stdout来读取行。
  4. 现在您可以关闭写管道,并且读取线程将正常关闭。

    simple_io_thread

    现在通过

    queue = Queue.Queue(500)
    r, w = os.pipe()
    process = subprocess.Popen(
        command,
        stdout=w,
        stderr=subprocess.STDOUT)
    iothread = threading.Thread(
        target=simple_io_thread,
        args=(os.fdopen(r), queue))
    iothread.daemon=True
    iothread.start()
    

    您可以关闭管道,os.close(w) 将关闭,没有任何例外。