2015-04-25 40 views
7

Próbuję zrozumieć bibliotekę asyncio, w szczególności z użyciem gniazd. Napisałem trochę kodu, próbując zdobyć zrozumienie, chciałem uruchomić asynchronicznie gniazdo nadajnika i odbiornika. Doszedłem do punktu, w którym wszystkie dane są wysyłane do ostatniego, ale potem muszę uruchomić jeszcze jedną pętlę. Patrząc jak to zrobić, znalazłem this link from stackoverflow, który zaimplementowałem poniżej - ale co tu się dzieje? Czy istnieje lepszy/bardziej rozsądny sposób, aby to zrobić, niż zadzwonić pod numer stop, a następnie run_forever?python asyncio uruchomić raz pętlę zdarzeń?

Dokumentacja stop() w pętli zdarzeń jest:

Zatrzymaj uruchomiony pętlę zdarzeń.

Każde wywołanie zwrotne zaplanowane przed zatrzymaniem() zostanie uruchomione. Callbacki zaplanowane po wywołaniu stop() nie działają. Jednak te wywołania zwrotne zostaną uruchomione, jeśli funkcja run_forever() zostanie wywołana później.

I run_forever() „s dokumentacja jest:

Run aż stop() jest wywoływana.

Pytania:

  • dlaczego na świecie jest run_forever jedynym sposobem run_once? To nawet nie ma sensu.
  • Czy jest lepszy sposób to zrobić?
  • Czy mój kod wygląda jak rozsądny sposób programowania z biblioteką asyncio?
  • Czy istnieje lepszy sposób dodawania zadań do pętli zdarzeń oprócz asyncio.async()? loop.create_task podaje błąd w moim systemie Linux.

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

Odpowiedz

10

stop(); run_forever() sztuczka działa, ponieważ, jak stop jest realizowany:

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 

Więc następnym razem biegnie pętla zdarzenie i wykonuje oczekujące wywołania zwrotne, to będzie zadzwonić _raise_stop_error, co podnosi _StopError. Pętla run_forever złamie tylko na tym konkretnym wyjątkiem:

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 

Tak, przez planowania stop() a następnie wywołanie run_forever, możesz skończyć z systemem jednej iteracji pętli zdarzeń, a następnie zatrzymanie raz natrafi _raise_stop_error zwrotnego. Być może zauważyłeś także, że _run_once jest zdefiniowane i wywołane przez run_forever. Możesz to wywołać bezpośrednio, ale czasami może to być blokowane, jeśli nie ma żadnych uruchomionych wywołań zwrotnych, co może nie być pożądane. Nie sądzę, że jest to obecnie lepszy sposób - Ta odpowiedź została dostarczona przez Andrew Svetlova, który jest współpracownikiem asyncio; prawdopodobnie wiedziałby, czy jest lepsza opcja.:)

Generalnie Twój kod wygląda rozsądnie, ale myślę, że nie powinieneś używać tego podejścia na początku. To nie jest deterministyczne; jeśli masz dłuższą listę lub wolniejszy system, może to wymagać więcej niż dwóch dodatkowych iteracji, aby wydrukować wszystko. Zamiast tego, należy po prostu wysłać Sentinel, który mówi odbiornik do wyłączenia, a następnie czekać na obu wysyłania i odbierania współprogram skończyć:

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() 

Wreszcie asyncio.async jest właściwym sposobem na dodanie zadania do pętli zdarzeń . create_task został dodany w Pythonie 3.4.2, więc jeśli masz wcześniejszą wersję, to nie będzie istnieć.

+0

Sądzę, że po prostu bardzo spodobał mi się pomysł "pełnej kontroli" w pętli zdarzeń, aby zrozumieć, jak to działało w pierwszej kolejności. Podoba mi się użycie SENTINAL, to ma sens :) – vitiral

Powiązane problemy