如何在后台 运行 EXE 程序并在 python 中获取输出

How to run an EXE program in the background and get the outuput in python

我想在后台运行一个exe程序
假设程序是 httpd.exe
我可以 运行 它但是当我想要输出时它会卡住,因为如果它成功启动则没有输出。但是如果有错误也没关系。

这是我使用的代码:

import asyncio
import os

os.chdir('c:\apache\bin')
process, stdout, stderr = asyncio.run(run('httpd.exe'))
print(stdout, stderr)

async def run(cmd):
    proc = await asyncio.create_subprocess_exec(
        cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    stdout, stderr = await proc.communicate()

    return (proc, stdout, stderr)

我试图使以下代码尽可能通用:

  1. 我不假设 运行 程序是只将其输出单独写入 stdout 还是单独写入 stderr。因此,我通过启动两个线程来捕获两个输出,每个线程一个,然后将输出写入一个可以实时读取的公共队列。当在每个 stdout 和 stderr 上遇到 end-of-stream 时,线程将一个特殊的 None 记录写入队列以指示流结束。所以队列的 reader 知道在看到两个这样的“流结束”指示器后,将不再有行写入队列并且进程已经有效结束。
  2. 可以使用参数 shell=True 调用 subprocess.Popen 这样也可以 built-in shell命令,也使命令的规范更容易(它现在可以是单个字符串而不是字符串列表)。
  3. 函数run_cmdreturns创建进程和队列。您现在只需要循环读取队列中的行,直到看到两个 None 记录。一旦发生这种情况,您就可以等待该过程完成,这应该是立竿见影的。
  4. 如果您知道您正在启动的进程只将其输出写入 stdout 或 stderr(或者如果您只想捕获这些输出之一),那么您可以修改程序以仅启动一个线程并指定subprocess.PIPE 仅这些输出之一的值,然后从队列中读取行的循环应该只寻找一个 None end-of-stream 指标。
  5. 线程是 daemon 线程,因此如果您希望根据在检测到所有 end-of-stream 记录之前已读取的进程输出终止,然后线程将与主进程一起自动终止。
  6. run_apache,其中运行s Apache 作为一个子进程,本身就是一个守护线程。如果它检测到来自 Apache 的任何输出,它会设置一个已传递给它的事件。启动 run_apache 的主线程可以定期测试此事件,等待此事件,等待 run_apache 线程结束(只有在 Apache 结束时才会发生)或者可以通过全局变量终止 Apache proc.
import subprocess
import sys
import threading
import queue


def read_stream(f, q):
    for line in iter(f.readline, ''):
        q.put(line)
    q.put(None) # show no more data from stdout or stderr

def run_cmd(command, run_in_shell=True):
    """
    Run command as a subprocess. If run_in_shell is True, then
    command is a string, else it is a list of strings.
    """
    proc = subprocess.Popen(command, shell=run_in_shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    q = queue.Queue()
    threading.Thread(target=read_stream, args=(proc.stdout, q), daemon=True).start()
    threading.Thread(target=read_stream, args=(proc.stderr, q), daemon=True).start()
    return proc, q

import os

def run_apache(event):
    global proc

    os.chdir('c:\apache\bin')
    proc, q = run_cmd(['httpd.exe'], False)
    seen_None_count = 0
    while seen_None_count < 2:
        line = q.get()
        if line is None:
            # end of stream from either stdout or stderr
            seen_None_count += 1
        else:
            event.set() # Seen output line:
            print(line, end='')
    # wait for process to terminate, which should be immediate:
    proc.wait()

# This event will be set if Apache write output:
event = threading.Event()
t = threading.Thread(target=run_apache, args=(event,), daemon=True)
t.start()
# Main thread runs and can test event any time to see if it has done any output:
if event.is_set():
    ...
# The main thread can wait for run_apache thread to normally terminate,
# will occur when Apache terminates:
t.join()
# or the main thread can kill Apache via global variable procL
proc.terminate() # No need to do t.join() since run_apache is a daemon thread