Асинхронная версия встроенной печати (стандартный вывод) - PullRequest
2 голосов
/ 07 ноября 2019

У меня проблема с пониманием некоторых ограничений при использовании print внутри функции async. В основном это мой код:

#!/usr/bin/env python
import sys
import asyncio
import aiohttp

async amain(loop):
    session = aiohttp.ClientSession(loop=loop)

    try:
        # using session to fetch a large json file wich is stored
        # in obj

        print(obj)  # for debugging purposes

    finally:
        await session.close()


def main():
    loop = asyncio.get_event_loop()

    res = 1

    try:
        res = loop.run_until_complete(amain(loop, args))
    except KeyboardInterrupt:
        # silence traceback when pressing ctrl+c
        pass

    loop.close()

    return res


if __name__ == '__main__':
    sys.exit(main())

Если я выполню это, тогда объект json будет напечатан на stdout, и внезапно умрет с этой ошибкой

$ dwd-get-sensor-file ; echo $?
Traceback (most recent call last):
  File "/home/yanez/anaconda/py3/envs/mondas/bin/dwd-get-sensor-file", line 11, in <module>
    load_entry_point('mondassatellite', 'console_scripts', 'dwd-get-sensor-file')()
  File "/home/yanez/projects/mondassatellite/mondassatellite/mondassatellite/bin/dwd_get_sensor_file.py", line 75, in main
    res = loop.run_until_complete(amain(loop, args))
  File "/home/yanez/anaconda/py3/envs/mondas/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
    return future.result()
  File "/home/yanez/projects/mondassatellite/mondassatellite/mondassatellite/bin/dwd_get_sensor_file.py", line 57, in amain
    print(obj)
BlockingIOError: [Errno 11] write could not complete without blocking
1

Самое смешное,что когда я выполняю свой код, перенаправляющий stdout в файл, подобный этому

$ dwd-get-sensor-file > output.txt ; echo $?
0

, исключение не происходит, и весь вывод корректно перенаправляется на output.txt.

Для тестированияя преобразовал объект json в строку и вместо print(obj) я делаю sys.stdout.write(obj_as_str), тогда получаю следующее исключение:

BlockingIOError: [Errno 11] write could not complete without blocking
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>

Я искал это исключение BlockingIOError, но все потоки яfind имеет отношение к сетевым сокетам или сборкам CI. Но я нашел один интересный комментарий github :

make: write error почти наверняка EAGAIN от stdout. Практически каждый инструмент командной строки ожидает, что stdout будет в режиме блокировки, и не будет правильно повторять попытку в неблокирующем режиме.

Поэтому, когда я выполнил это

python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); print(flags&os.O_NONBLOCK);'

, я получил 2048,что означает блокирование (или это наоборот? Я в замешательстве). После выполнения этого

python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);'

я больше не получаю BlockingIOError исключений, но мне не нравится это решение.

Итак, мой вопрос: как нам поступитьпри записи в stdout внутри функции async? Если я знаю, что имею дело с stdout, должен ли я установить stdout в неблокирующее состояние и вернуть его обратно при выходе из программы? Есть ли конкретная стратегия для этого?

1 Ответ

0 голосов
/ 07 ноября 2019

Дайте aiofiles попробовать, используя stdout FD в качестве файлового объекта.

aiofiles помогает с этим, представляя асинхронные версии файлов, которые поддерживают делегирование операций в отдельный пул потоков.

С точки зрения фактического использования aiofiles непосредственно с FD, вы, вероятно, могли бы расширить aiofiles.os модуль , используя wrap(os.write).

...