Неблокирующий ввод-вывод с асинхронным
Я пытаюсь написать сетевую игру с Pygame и asyncio, но я не могу понять, как избежать зависания при чтении. Вот мой код для клиента:
@asyncio.coroutine
def handle_client():
print("Connected!")
reader, writer = yield from asyncio.open_connection('localhost', 8000)
while True:
mouse_up = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONUP:
mouse_up = True
if mouse_up:
print("Writing")
writer.write(b"Mouse up")
print("Waiting to read")
line = yield from reader.read(2**12)
print(line.decode())
writer.close()
Это зависает на линии line = yield from reader.read(2**12)
. Ранее я думал, что точкой асинчо было то, что он был неблокирующим, и поэтому, если бы не было никаких данных для чтения, это просто продолжало бы выполнение. Теперь я вижу, что это не так.
Как интегрировать сетевой код asyncio с чертежом Pygame и кодом события?
Ответы
Ответ 1
Точка yield from
- это переключить выполнение в цикл событий asyncio и заблокировать текущую сопрограмму до тех пор, пока результат не будет доступен. Чтобы запланировать задачу без блокировки текущей сопрограммы, вы можете использовать asyncio.async()
.
Чтобы распечатать данные с так называемой "далекой точки", не блокируя цикл pygame:
@asyncio.coroutine
def read(reader, callback):
while True:
data = yield from reader.read(2**12)
if not data: # EOF
break
callback(data)
@asyncio.coroutine
def echo_client():
reader, ...
chunks = []
asyncio.async(read(reader, chunks.append))
while True:
pygame.event.pump() # advance pygame event loop
...
if chunks: # print read-so-far data
print(b''.join(chunks).decode())
del chunks[:]
yield from asyncio.sleep(0.016) # advance asyncio loop
Там не должно быть никаких блокирующих вызовов внутри в while
цикла.
read()
и sleep()
сопроводится одновременно в одном потоке (очевидно, вы можете одновременно запускать другие сопрограммы).
Ответ 2
Вы можете "преобразовать" задачу блокировки в неблокирующую.
Я предлагаю это: https://docs.python.org/3/library/asyncio-eventloop.html#executor.
У меня есть функция, которая слушает твиттер-канал, функцию "упоминать", и я запускаю его в исполнителе, поэтому, если он зависает, он не блокирует другие задачи.
@asyncio.coroutine
def boucle_deux():
#faire attendre la boucle si pas bcp de mots
while True:
print("debut du deux")
value = t.next()
future2 = loop.run_in_executor(None, mention, "LQNyL2xvt9OQMvje7jryaHkN8",
"IRJX6S17K44t8oiVGCjrj6XCVKqGSX9ClfpGpfC467rajqePGb",
"2693346740-km3Ufby8r9BbYpyzcqwiHhss22h4YkmnPN4LnLM",
"53R8GAAncFJ1aHA1yJe1OICfjqUbqwcMR38wSqvbzsQMB", 23, value)
response2 = yield from future2
yield from asyncio.sleep(5)
print("fin du deux")
asyncio.Task(boucle_deux())
Ответ 3
так как вы пытаетесь прочитать значение "линии" сразу после вызова функции read(), вам нужно это значение любой ценой...
если сопрограмма не остановится, потому что нет данных, вы можете получить AttributeError в вызове line.decode(), если "строка" - это None.
одна вещь, которую вы можете сделать, - установить тайм-аут блокирующего вызова и обработать исключение таймаута:
...
print("Waiting to read")
try: # block at most for one second
line = yield from asyncio.wait_for(reader.read(2**12), 1)
except asyncio.TimeoutError:
continue
else:
print(line.decode())
...