2015-04-25 4 views
7

Я пытаюсь понять асинхронную библиотеку, особенно с использованием сокетов. Я написал код в попытке получить понимание,python asyncio запускает цикл событий один раз?

Я хотел запускать отправителя и приемные гнезда асинхронно. Я дошел до того, что получил все данные, отправленные до последнего, но затем мне нужно запустить еще один цикл. Глядя на то, как это сделать, я нашел this link from stackoverflow, который я реализовал ниже - но что здесь происходит? Есть ли лучший/более разумный способ сделать это, чем позвонить stop, а затем run_forever?

Документация stop() в цикл обработки событий:

Stop работает цикл обработки событий.

Выполняется каждый обратный вызов, запланированный до вызова функции stop(). Обратные вызовы, запланированные после вызова функции stop(), не будут выполняться. Тем не менее, эти обратные вызовы будут выполняться, если run_forever() вызывается снова позже.

И run_forever() «s документация:

Run до упора() вызывается.

Вопросы:

  • почему в мире run_forever единственный способ run_once? Это даже не имеет смысла
  • Есть ли лучший способ сделать это?
  • Имеет ли мой код разумный способ программирования с помощью библиотеки асинхронных игр?
  • Есть ли лучший способ добавить задачи в цикл событий, кроме asyncio.async()? loop.create_task дает ошибку в моей системе Linux.

https://gist.github.com/cloudformdesign/b30e0860497f19bd6596

ответ

10

stop(); run_forever() трюк работает из-за того, как stop реализуется:

def stop(self): 
    """Stop running the event loop. 

    Every callback scheduled before stop() is called will run. 
    Callback scheduled after stop() is called won't. However, 
    those callbacks will run if run() is called again later. 
    """ 
    self.call_soon(_raise_stop_error) 

def _raise_stop_error(*args): 
    raise _StopError 

Так, в следующий раз, когда запускается цикл обработки событий и выполняет ожидающие обратного вызова, он собирается назвать _raise_stop_error, что повышает _StopError. Цикл run_forever сломается только на конкретном исключением:

def run_forever(self): 
    """Run until stop() is called.""" 
    if self._running: 
     raise RuntimeError('Event loop is running.') 
    self._running = True 
    try: 
     while True: 
      try: 
       self._run_once() 
      except _StopError: 
       break 
    finally: 
     self._running = False 

Так, запланировав stop() и затем вызвать run_forever, вы в конечном итоге работает один итерации цикла событий, затем остановка, как только он попадает в _raise_stop_error обратного вызова. Возможно, вы также заметили, что _run_once определяется и вызывается run_forever. Вы можете называть это напрямую, но иногда это может блокироваться, если нет готовых к запуску обратных вызовов, что может быть нежелательно. Я не думаю, что сейчас есть более чистый способ сделать это. Этот ответ был предоставлен Эндрю Светловым, который является участником asyncio; он, вероятно, знал бы, есть ли лучший вариант.:)

В целом, ваш код выглядит разумным, хотя я думаю, что вы не должны использовать этот подход run_once для начала. Это не детерминировано; если у вас был более длинный список или более медленная система, для печати всего может потребоваться более двух дополнительных итераций. Вместо этого, вы должны просто послать часовую, который говорит приемнику выключить, а затем ждать как отправлять и получать сопрограммы закончить:

import sys 
import time 
import socket 
import asyncio 


addr = ('127.0.0.1', 1064) 
SENTINEL = b"_DONE_" 

# ... (This stuff is the same) 

@asyncio.coroutine 
def sending(addr, dataiter): 
    loop = asyncio.get_event_loop() 
    for d in dataiter: 
     print("Sending:", d) 
     sock = socket.socket() 
     yield from send_close(loop, sock, addr, str(d).encode()) 
    # Send a sentinel 
    sock = socket.socket() 
    yield from send_close(loop, sock, addr, SENTINEL) 

@asyncio.coroutine 
def receiving(addr): 
    loop = asyncio.get_event_loop() 
    sock = socket.socket() 
    try: 
     sock.setblocking(False) 
     sock.bind(addr) 
     sock.listen(5) 

     while True: 
      data = yield from accept_recv(loop, sock) 
      if data == SENTINEL: # Got a sentinel 
       return 
      print("Recevied:", data) 
    finally: sock.close() 

def main(): 
    loop = asyncio.get_event_loop() 
    # add these items to the event loop 
    recv = asyncio.async(receiving(addr), loop=loop) 
    send = asyncio.async(sending(addr, range(10)), loop=loop) 
    loop.run_until_complete(asyncio.wait([recv, send])) 

main() 

Наконец, asyncio.async правильный способ добавления задач в цикл обработки событий , create_task был добавлен в Python 3.4.2, поэтому, если у вас есть более ранняя версия, он не будет существовать.

+0

Я думаю, мне просто очень понравилась идея иметь «полный контроль» в цикле событий, чтобы понять, как это работает в первую очередь. Мне нравится использование SENTINAL, хотя это имеет смысл :) – vitiral