Python3 asyncio:wait_for()communication()有超时,如何获得部分结果?

时间:2017-03-07 04:02:26

标签: python subprocess python-asyncio

The Python docs about asyncio - Subprocess say:

  

using System.ComponentModel; namespace WpfApplication1.ViewModels { public abstract class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected string _title; protected string Title { get { return _title; } set { _title = value; OnPropertyChanged(nameof(Title)); } } protected void OnPropertyChanged(string propName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); } } } communicate()方法不使用超时参数:使用wait()函数

使用wait_for()communicate()上施加超时非常容易,但我找不到从中断的wait_for()调用中检索部分结果的方法,以及后续调用communicate()也不会返回丢失的部分。

示例脚本:

communicate()

示例脚本的输出:

#! /usr/bin/env python3

import asyncio

async def communicate_short(loop):
    p = await asyncio.create_subprocess_exec('ping', '127.0.0.1', '-n', '4', stdout=asyncio.subprocess.PIPE)
    # For Linux: use '-c' instead of '-n'

    try:
        # 2 seconds timeout
        res = await asyncio.wait_for(p.communicate(), 2)
    except asyncio.TimeoutError as e:
        # After timeout happens:
        # How do I get the subprocess's STDOUT up to this point?
        try:
            print(res[0].decode('utf-8'))
            # Will raise NameError since the communicate() call did not complete
        except NameError as e:
            print('NameError: %s' % e)


    res = await p.communicate()
    print(res[0].decode('utf-8'))
    # Only prints the later half of ping's STDOUT

if __name__ == '__main__':
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
    # For Linux: just do loop = asyncio.get_event_loop()

    loop.run_until_complete(communicate_short(loop))

请注意,仅打印最后2个数据包。关于前2个数据包的输出会丢失。

那么,如何在超时发生之前从子进程获取输出?

编辑:更确切地说,理想情况下我正在寻找的东西:

  1. NameError: local variable 'res' referenced before assignment Reply from 127.0.0.1: bytes=32 time<1ms TTL=128 Reply from 127.0.0.1: bytes=32 time<1ms TTL=128 Ping statistics for 127.0.0.1: Packets: Sent = 4, Received = 4, Lost = 0 (0% loss), Approximate round trip times in milli-seconds: Minimum = 0ms, Maximum = 0ms, Average = 0ms 做什么,即异步写入子进程的STDIN并读取其STDOUT和STDERR,而不会出现死锁(that the docs ominously warn about);

  2. 具有可配置的总超时,因此当子进程终止或达到超时时,返回到目前为止收到的STDOUT和STDERR。

  3. 看起来这样的东西还不存在,而且必须实现它。

3 个答案:

答案 0 :(得分:1)

您必须使用process.stdout

data = await process.stdout.readline()

请参阅user documentation中的示例。

答案 1 :(得分:0)

对于问题的第二部分,“如何在超时发生之前从子进程获取输出?”我建议使用不取消任务的asyncio.wait()p.communicate())代替asyncio.wait_for()(取消任务):

task = asyncio.Task(p.communicate())
done, pending = await asyncio.wait([task], timeout=2)
if pending:
    print("timeout!", task._state)
res = await task  # Note: It is OK to await a task more than once
print(res[0].decode())

关于“检索部分结果”,我建议不要使用调用communicate()的{​​{1}}并使用其他方法:

stdout.read()

组合这两个解决方案(并使用import asyncio async def ping(loop, host): p = await asyncio.create_subprocess_exec( 'ping', host, '-c', '4', stdout=asyncio.subprocess.PIPE, loop=loop) async for line in p.stdout: print(host, "==>", line.decode(), end="") print(host, "done") if __name__ == '__main__': loop = loop = asyncio.get_event_loop() asyncio.set_event_loop(loop) tasks = [ ping(loop, '8.8.8.8'), ping(loop, '127.0.0.1'), ping(loop, 'example.com'), ] loop.run_until_complete(asyncio.wait(tasks)) loop.close() 而不是更冷的异步)给出:

readline()

注意超时(1秒)是每行。

另请参阅:https://github.com/aio-libs/async-timeout

答案 2 :(得分:0)

如果在超时后杀死子进程,则可以获得如下的部分输出:

future = asyncio.ensure_future(p.communicate())
done, pending = await asyncio.wait([future], timeout=2)
if pending:
    # timeout
    if p.returncode is None:
        # kill the subprocess, then `await future` will return soon
        try:
            p.kill()
        except ProcessLookupError:
            pass
output, err = await future
print(output.decode('utf-8'))