如何检查 Windows 和 Python 3.7+ 中的 asyncio StreamReader 是否要读取某些内容?

How can I check if something to read in asyncio StreamReader in Windows and Python 3.7+?

我尝试与 Stockfish 通信(它是 stdin/stdout 应用程序)。

它是这样工作的:

  1. 我将一些行发送到标准输入。
  2. 它立即将一些行响应到标准输出。
  3. 它可以连续响应下一行(新动作评估)。
  4. Stockfish 永远不会停止,所以 communicate() 不会工作,因为它不是设计者。 add_reader 不适用于 Windows。

正在连续发送不可预测的行数。

问题是当我调用 await stdout.readline() 时它是否阻塞了程序流我不想要阻塞流但检查是否读取了新行。我怎样才能不阻塞地读取行。

我做的一些代码——没什么复杂的:

import asyncio

import sys


def stockfish_read_stdout(line):
    print(line)


async def run_stockfish():
    STOCKFISH_PATH = r'C:\root\chess\stockfish\stockfish 9\stockfish_9_x64_bmi2.exe'

    stockfish = await asyncio.subprocess.create_subprocess_exec(
        STOCKFISH_PATH,
        stdin=asyncio.subprocess.PIPE,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    # It is maybe not work since output is not complete.
    stockfish.stdin.write('uci'.encode())

    # How to check if line read to read?
    # I put True but here should be check.
    while True:
        line = await stockfish.stdout.readline()
        print(line)

    await stockfish.wait()


if sys.platform == "win32":
    asyncio.set_event_loop_policy(
        asyncio.WindowsProactorEventLoopPolicy())

asyncio.run(run_stockfish(), debug=True)

输出为:

Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:57:15) [MSC v.1915 64 bit (AMD64)] on win32
runfile('C:/Users/Cezary Wagner/PycharmProjects/stockfish-proxy/sandbox/async_proxy/s01_async_stockfish.py', wdir='C:/Users/Cezary Wagner/PycharmProjects/stockfish-proxy/sandbox/async_proxy')
b'Stockfish 9 64 BMI2 by T. Romstad, M. Costalba, J. Kiiski, G. Linscott\r\n'

但应该在uci之后:

Stockfish 9 64 BMI2 by T. Romstad, M. Costalba, J. Kiiski, G. Linscott
uci
id name Stockfish 9 64 BMI2
id author T. Romstad, M. Costalba, J. Kiiski, G. Linscott

option name Debug Log File type string default
option name Contempt type spin default 20 min -100 max 100
option name Threads type spin default 1 min 1 max 512
option name Hash type spin default 16 min 1 max 131072
option name Clear Hash type button
option name Ponder type check default false
option name MultiPV type spin default 1 min 1 max 500
option name Skill Level type spin default 20 min 0 max 20
option name Move Overhead type spin default 30 min 0 max 5000
option name Minimum Thinking Time type spin default 20 min 0 max 5000
option name Slow Mover type spin default 89 min 10 max 1000
option name nodestime type spin default 0 min 0 max 10000
option name UCI_Chess960 type check default false
option name SyzygyPath type string default 
option name SyzygyProbeDepth type spin default 1 min 1 max 100
option name Syzygy50MoveRule type check default true
option name SyzygyProbeLimit type spin default 6 min 0 max 6
uciok

您需要创建一个单独的协程,它将从进程的标准输出缓冲区读取行,因为 await 阻止 当前 协程执行,直到收到结果。

async def reader(process, msg_handler, exit_handler):
    while not process.stdout.at_eof():
        line = await process.stdout.readline()
        msg_handler(line.decode())
    # Finished
    exit_handler()

现在您可以运行它作为事件循环中的任务。

async def run_stockfish():
    STOCKFISH_PATH = r'C:\root\chess\stockfish\stockfish 9\stockfish_9_x64_bmi2.exe'

    stockfish = await asyncio.subprocess.create_subprocess_exec(
        STOCKFISH_PATH,
        stdin=asyncio.subprocess.PIPE,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    # It is maybe not work since output is not complete.
    stockfish.stdin.write('uci\n'.encode())

    task = asyncio.create_task(reader(stockfish,
                                      lambda msg: print(f'Output: {msg}'),
                                      lambda: print('EOF'))

    # asyncio.create_task won't block this coroutine

希望还不算太晚。