Asyncio клавиатурный ввод, который можно отменить - PullRequest
2 голосов
/ 21 октября 2019

Я пытаюсь написать параллельную программу на Python, используя asyncio, которая также принимает ввод с клавиатуры. Проблема появляется, когда я пытаюсь закрыть свою программу. Поскольку ввод с клавиатуры, в конце концов, выполняется с помощью sys.stdin.readline, эта функция возвращается только после того, как я нажму ENTER, независимо от того, буду ли я stop() цикл обработки событий или cancel() функции * Future.

Есть ли способ обеспечить ввод с клавиатуры с помощью asyncio, который можно отменить?

Вот мой MWE. Он будет принимать ввод с клавиатуры в течение 1 секунды, затем stop():

import asyncio
import sys

async def console_input_loop():
    while True:
        inp = await loop.run_in_executor(None, sys.stdin.readline)
        print(f"[{inp.strip()}]")

async def sleeper():
    await asyncio.sleep(1)
    print("stop")
    loop.stop()

loop = asyncio.get_event_loop()
loop.create_task(console_input_loop())
loop.create_task(sleeper())
loop.run_forever()

1 Ответ

1 голос
/ 21 октября 2019

Проблема в том, что исполнитель настаивает на том, чтобы все запущенные фьючерсы были завершены к моменту завершения программы. Но в этом случае вам действительно нужно «нечистое» завершение, потому что нет переносимого способа отмены текущего read() или асинхронного доступа к sys.stdin.

Отмена будущего не имеет никакого эффекта, потому что concurrent.futures.Future.cancel не работает, когда его обратный вызов начал выполняться. Лучший способ избежать нежелательного ожидания - это в первую очередь избегать run_in_executor и просто создавать собственный поток:

async def ainput():
    loop = asyncio.get_event_loop()
    fut = loop.create_future()
    def _run():
        line = sys.stdin.readline()
        loop.call_soon_threadsafe(fut.set_result, line)
    threading.Thread(target=_run, daemon=True).start()
    return await fut

Поток создается вручную и помечается как "демон", поэтому никто не будетдождитесь его при закрытии программы. В результате вариант кода, который использует ainput вместо run_in_executor(sys.stdin.readline), завершается, как и ожидалось:

async def console_input_loop():
    while True:
        inp = await ainput()
        print(f"[{inp.strip()}]")

# rest of the program unchanged
...