Python - Running Autobahn | Python Asyncio WebSocket Server in una sottoprocessa separata o in thread

StackOverflow https://stackoverflow.com//questions/25063403

Domanda

Ho un programma GUI basato su Tkinter in esecuzione in Python 3.4.1. Ho diversi thread in esecuzione nel programma per ottenere dati JSON da vari URL. Volendo aggiungere alcune funzionalità di websocket per poter consentire al programma di fungere da server e consentire a diversi client di connettersi su un WebSocket e scambiare altri dati JSON.

Sto tentando di utilizzare Autobahn | Python WebSocket Server per ASYNCIO.

Ho cercato per la prima volta di eseguire il ciclo di eventi Asyncio in un filo separato sotto il programma GUI. Tuttavia, ogni tentativo dà "AssertionError: non esiste un ciclo di evento corrente nel filo 'Thread-1'.

Ho quindi provato a redigere un processo con il pacchetto multiprocessing della libreria standard che ha eseguito il ciclo di eventi Asyncio in un altro processo. Quando provo questo non ho alcuna eccezione, ma il server WebSocket non si avvia neanche.

è persino possibile eseguire un ciclo di eventi Asyncio in una sottoprocesso da un altro programma Python?

C'è anche un modo per integrare un ciclo di eventi Asyncio in un programma attualmente multithreaded / tkinter?

Aggiornamento Di seguito è riportato il codice attuale che sto cercando di funzionare per un test iniziale.

from autobahn.asyncio.websocket import WebSocketServerProtocol
from autobahn.asyncio.websocket import WebSocketServerFactory
import asyncio
from multiprocessing import Process

class MyServerProtocol(WebSocketServerProtocol):

   def onConnect(self, request):
      print("Client connecting: {0}".format(request.peer))

   def onOpen(self):
      print("WebSocket connection open.")

   def onMessage(self, payload, isBinary):
      if isBinary:
         print("Binary message received: {0} bytes".format(len(payload)))

      else:
         print("Text message received: {0}".format(payload.decode('utf8')))

      ## echo back message verbatim
      self.sendMessage(payload, isBinary)

   def onClose(self, wasClean, code, reason):
      print("WebSocket connection closed: {0}".format(reason))

def start_server():
   factory = WebSocketServerFactory("ws://10.241.142.27:6900", debug = False)
   factory.protocol = MyServerProtocol
   loop = asyncio.get_event_loop()
   coro = loop.create_server(factory, '10.241.142.27', 6900)
   server = loop.run_until_complete(coro)
   loop.run_forever()
   server.close()
   loop.close()


websocket_server_process = Process(target = start_server)
websocket_server_process.start()
.

La maggior parte di esso è direttamente dal codice di esempio Autobahn | Python per Asyncio. Se provo a eseguirlo come un processo non fa nulla, nessun client può connettersi ad esso, se eseguo Netstat -a non è presente alcuna porta 6900. Se basta utilizzare Start_Server () nel programma principale crea il server WebSocket.

È stato utile?

Soluzione

In primo luogo, stai ricevendo AssertionError: There is no current event loop in thread 'Thread-1'. perché asyncio richiede a ciascun thread nel tuo programma per avere il proprio ciclo di eventi, ma creerà automaticamente un ciclo di evento solo per te nel thread principale. Quindi, se si chiama asyncio.get_event_loop una volta nel thread principale creerà automaticamente un oggetto loop e impostalo come predefinito per te, ma se lo chiami di nuovo in un filo infantile, riceverai quell'errore. Invece, è necessario creare / impostare esplicitamente il loop dell'evento quando inizia il thread:

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
.

Una volta che lo hai fatto, dovresti essere in grado di utilizzare get_event_loop() in quella filettatura specifica.

È possibile avviare un anello di evento asyncio in una sottoprocesso avviato tramite multiprocessing:

import asyncio
from multiprocessing import Process 

@asyncio.coroutine
def coro():
    print("hi")

def worker():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(coro())

if __name__ == "__main__":
    p = Process(target=worker)
    p.start()
    p.join()
.

Uscita:

hi
.

L'unico avvertimento è che se si avvia un ciclo di eventi nel processo genitore e il bambino, è necessario creare esplicitamente / impostare un nuovo ciclo di eventi nel bambino se sei su una piattaforma UNIX (a causa di A Unix Bug in Python ). Dovrebbe funzionare bene su Windows, o se si utilizza il contesto multiprocessing "Spawn".

Penso che dovrebbe essere possibile avviare un ciclo di eventi asyncio in un thread di sfondo (o processo) dell'applicazione Tkinter e avere sia il tkinter e il ciclo di evento asyncio. Verificherai solo problemi se si tenta di aggiornare la GUI dal filo / processo di sfondo.

Altri suggerimenti

La risposta di @DANO potrebbe essere corretto, ma crea un nuovo processo che è laureatario nella maggior parte delle situazioni.

Ho trovato questa domanda su Google perché ho avuto lo stesso problema da solo.Ho scritto un'applicazione in cui volevo che un'API WebSocket non viene eseguita sul thread principale e questo ha causato il problema.

Ho trovato il mio soluzioni alternativo semplicemente leggendo sui loop di eventi sulla documentazione di Python e ha trovato le funzioni Asyncio.new_event_loop e asyncio.set_loop_loop che ha risolto questo problema.

Non ho usato Autobahn ma la libreria PYPI WebSechets, ed ecco la mia soluzione

import websockets
import asyncio
import threading

class WebSocket(threading.Thread):    
    @asyncio.coroutine
    def handler(self, websocket, path):
        name = yield from websocket.recv()
        print("< {}".format(name))
        greeting = "Hello {}!".format(name)
        yield from websocket.send(greeting)
        print("> {}".format(greeting))

    def run(self):
        start_server = websockets.serve(self.handler, '127.0.0.1', 9091)
        eventloop = asyncio.new_event_loop()
        asyncio.set_event_loop(eventloop)
        eventloop.run_until_complete(start_server)
        eventloop.run_forever()

if __name__ == "__main__":
    ws = WebSocket()
    ws.start()
.

"C'è anche un modo per integrare un ciclo di eventi Asyncio in un programma attualmente multithreaded / tkinter?"

Sì, esegui il tuo programma Tkinter con un loop evento Asyncio.Prova del concetto.

'''Proof of concept integrating asyncio and tk loops.

Terry Jan Reedy
Run with 'python -i' or from IDLE editor to keep tk window alive.
'''

import asyncio
import datetime as dt
import tkinter as tk

loop = asyncio.get_event_loop()
root = tk.Tk()

# Combine 2 event loop examples from BaseEventLoop doc.
# Add button to prove that gui remain responsive between time updates.
# Prints statements are only for testing.

def flipbg(widget, color):
    bg = widget['bg']
    print('click', bg, loop.time())
    widget['bg'] = color if bg == 'white' else 'white'

hello = tk.Label(root)
flipper = tk.Button(root, text='Change hello background', bg='yellow',
                    command=lambda: flipbg(hello, 'red'))
time = tk.Label(root)
hello.pack()
flipper.pack()
time.pack()

def hello_world(loop):
    hello['text'] = 'Hello World'
loop.call_soon(hello_world, loop)

def display_date(end_time, loop):
    print(dt.datetime.now())
    time['text'] = dt.datetime.now()
    if (loop.time() + 1.0) < end_time:
        loop.call_later(1, display_date, end_time, loop)
    else:
        loop.stop()

end_time = loop.time() + 10.1
loop.call_soon(display_date, end_time, loop)

# Replace root.mainloop with these 4 lines.
def tk_update():
    root.update()
    loop.call_soon(tk_update)  # or loop.call_later(delay, tk_update)
# Initialize loop before each run_forever or run_until_complete call    
tk_update() 
loop.run_forever()
.

Ho eseguito sperimentalmente inattivo con quelle 4 linee extra, con un rallentamento solo evidente quando la sintassi che evidenzia 1000 linee.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top