Python 模仿glib.spawn与Popen异步…;

Python 模仿glib.spawn与Popen异步…;,python,multithreading,gtk,glib,python-asyncio,Python,Multithreading,Gtk,Glib,Python Asyncio,该函数允许您钩住三个回调,分别在stdout、stderr事件和进程完成时调用 如何使用线程或异步IO模拟相同的功能 我更感兴趣的是功能,而不是线程/asynio,但是一个包含这两者的答案将获得奖励 下面是一个玩具程序,展示了我想做的事情: import glib import logging import os import gtk class MySpawn(object): def __init__(self): self._logger = logging.g

该函数允许您钩住三个回调,分别在
stdout
stderr
事件和进程完成时调用

如何使用线程或异步IO模拟相同的功能

我更感兴趣的是功能,而不是线程/asynio,但是一个包含这两者的答案将获得奖励

下面是一个玩具程序,展示了我想做的事情:

import glib
import logging
import os
import gtk


class MySpawn(object):
    def __init__(self):
        self._logger = logging.getLogger(self.__class__.__name__)

    def execute(self, cmd, on_done, on_stdout, on_stderr):
        self.pid, self.idin, self.idout, self.iderr = \
            glib.spawn_async(cmd,
                             flags=glib.SPAWN_DO_NOT_REAP_CHILD,
                             standard_output=True,
                             standard_error=True)
        fout = os.fdopen(self.idout, "r")
        ferr = os.fdopen(self.iderr, "r")
        glib.child_watch_add(self.pid, on_done)
        glib.io_add_watch(fout, glib.IO_IN, on_stdout)
        glib.io_add_watch(ferr, glib.IO_IN, on_stderr)
        return self.pid


if __name__ == '__main__':
    logging.basicConfig(format='%(thread)d %(levelname)s:  %(message)s',
                        level=logging.DEBUG)
    cmd = '/usr/bin/git ls-remote https://github.com/DiffSK/configobj'.split()

    def on_done(pid, retval, *args):
        logging.info("That's all folks!…")

    def on_stdout(fobj, cond):
        """This blocks which is fine for this toy example…"""
        for line in fobj.readlines():
            logging.info(line.strip())
        return True

    def on_stderr(fobj, cond):
        """This blocks which is fine for this toy example…"""
        for line in fobj.readlines():
            logging.error(line.strip())
        return True

    runner = MySpawn()
    runner.execute(cmd, on_done, on_stdout, on_stderr)
    try:
        gtk.main()
    except KeyboardInterrupt:
        print('')
我应该补充一点,因为
readlines()
是阻塞的,上面的代码将缓冲所有输出并立即发送。如果这不是您想要的,那么您必须使用
readline()
,并确保在命令结束时,您完成了以前未读过的所有行。

asyncio有,根本不需要使用子流程模块:

import asyncio

class Handler(asyncio.SubprocessProtocol):
    def pipe_data_received(self, fd, data):
        # fd == 1 for stdout, and 2 for stderr
        print("Data from /bin/ls on fd %d: %s" % (fd, data.decode()))

    def pipe_connection_lost(self, fd, exc):
        print("Connection lost to /bin/ls")

    def process_exited(self):
        print("/bin/ls is finished.")

loop = asyncio.get_event_loop()
coro = loop.subprocess_exec(Handler, "/bin/ls", "/")

loop.run_until_complete(coro)
loop.close()
使用子进程和线程,它也很简单。您只需为每个管道生成一个线程,并为进程生成一个到
wait()

import subprocess
import threading

class PopenWrapper(object):
    def __init__(self, args):
       self.process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)

       self.stdout_reader_thread = threading.Thread(target=self._reader, args=(self.process.stdout,))
       self.stderr_reader_thread = threading.Thread(target=self._reader, args=(self.process.stderr,))
       self.exit_watcher = threading.Thread(target=self._exit_watcher)

       self.stdout_reader_thread.start()
       self.stderr_reader_thread.start()
       self.exit_watcher.start()

    def _reader(self, fileobj):
        for line in fileobj:
            self.on_data(fileobj, line)

    def _exit_watcher(self):
        self.process.wait()
        self.stdout_reader_thread.join()
        self.stderr_reader_thread.join()
        self.on_exit()

    def on_data(self, fd, data):
        return NotImplementedError

    def on_exit(self):
        return NotImplementedError

    def join(self):
        self.process.wait()

class LsWrapper(PopenWrapper):
    def on_data(self, fd, data):
        print("Received on fd %r: %s" % (fd, data))

    def on_exit(self):
        print("Process exited.")


LsWrapper(["/bin/ls", "/"]).join()

但是,请注意glib不会使用线程异步执行回调。它使用事件循环,就像asyncio一样。其思想是,在程序的核心是一个循环,它等待某个事件发生,然后同步执行相关的回调。在您的例子中,这是“其中一个管道上的数据可以读取”和“子流程已退出”。一般来说,它还包括“X11服务器报告的鼠标移动”、“有传入的网络流量”等内容。您可以通过编写自己的事件循环来模拟glib的行为。在两个管道上使用。如果select报告管道是可读的,但
read
不返回任何数据,则进程可能已退出-在这种情况下,调用子进程对象上的
poll()
方法检查是否已完成,如果已完成,则调用退出回调,或其他错误回调。

非常感谢您花时间编写此答案。请注意,上述内容将缓冲
stdout
stderr
中的行,因为
readlines()
正在阻塞。如果您希望在更新时使用
read()
,但请确保在读卡器线程完成时清空缓冲区。