Python-exécution du serveur websocket Autobahn / Python asyncio dans un sous-processus ou un thread séparé

J'ai un programme GUI basé sur tkinter en cours D'exécution en python 3.4.1. J'ai plusieurs threads en cours d'exécution dans le programme pour obtenir des données JSON à partir de diverses URL. Je souhaite ajouter une fonctionnalité WebSocket pour permettre au programme d'agir en tant que serveur et permettre à plusieurs clients de s'y connecter via un WebSocket et d'échanger d'autres données JSON.

J'essaie d'utiliser le serveur websocket Autobahn / Python pour asyncio.

J'ai d'abord essayé d'exécuter la asyncio boucle d'événement dans un thread séparé en vertu de l'interface graphique du programme. Cependant, chaque tentative donne 'AssertionError :il N'y a pas de boucle d'événement en cours dans le thread 'Thread-1'.

J'ai ensuite essayé de générer un processus avec le paquet multiprocessing de bibliothèque standard qui exécutait la boucle d'événement asyncio dans un autre processus. Quand j'essaie cela, je ne reçois aucune exception, mais le serveur WebSocket ne démarre pas non plus.

Est-il même possible d'exécuter une boucle d'événement asyncio dans un sous-processus à partir d'un autre programme Python?

Y a-t-il même un moyen de intégrer une boucle d'événement asyncio dans un programme multithread/tkinter actuellement?

Mise à jour Voici le code réel que j'essaie d'Exécuter pour un test initial.

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 plus grande partie provient directement de L'exemple de code Autobahn / Python pour asyncio. Si j'essaie de l'exécuter en tant que Processus, il ne fait rien, aucun client ne peut s'y connecter, si je lance netstat-a, il n'y a pas de port 6900 utilisé. Si vous utilisez simplement start_server () dans le programme principal, il crée le serveur WebSocket.

21
demandé sur user2662241 2014-07-31 19:46:30

3 réponses

Tout d'abord, vous obtenez AssertionError: There is no current event loop in thread 'Thread-1'. parce que asyncio nécessite que chaque thread de votre programme ait sa propre boucle d'événement, mais il ne créera automatiquement qu'une boucle d'événement pour vous dans le thread principal. Donc, si vous appelez asyncio.get_event_loop Une fois dans le thread principal, il créera automatiquement un objet loop et le définira par défaut pour vous, mais si vous l'appelez à nouveau dans un thread enfant, vous obtiendrez cette erreur. Au lieu de cela, vous devez créer/définir explicitement la boucle d'événement lorsque le thread démarre:

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

Une fois que vous avez fait cela, vous devriez pouvoir utiliser get_event_loop() dans ce thread spécifique.

Il est possible de démarrer une boucle d'événement asyncio dans un sous-processus démarré via 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()

Sortie:

hi

, Le seul inconvénient est que si vous commencez une boucle dans le processus parent de l'enfant, vous avez besoin de créer explicitement/définir une nouvelle boucle d'événement de l'enfant si vous êtes sur une plate-forme Unix (suite à un bug en Python). Il devrait fonctionner sur Windows, ou si vous utilisez le 'spawn' multiprocessing cadre.

Je pense qu'il devrait être possible de démarrer une boucle d'événement asyncio dans un thread d'arrière-plan (ou un processus) de votre application Tkinter et de faire exécuter à la fois la boucle d'événement tkinter et asyncio côte à côte. Vous ne rencontrerez des problèmes que si vous essayez de mettre à jour l'interface graphique à partir du thread/processus d'arrière-plan.

26
répondu dano 2015-07-28 14:38:32

La réponse de @dano peut être correcte, mais crée un nouveau processus qui n'est pas nécessaire dans la plupart des situations.

J'ai trouvé cette question sur Google parce que j'avais moi-même le même problème. J'ai écrit une application où je voulais qu'une api websocket ne s'exécute pas sur le thread principal et cela a causé votre problème.

J'ai trouvé ma solution alternative en lisant simplement sur les boucles d'événements sur la documentation python et trouvé l'asyncio.new_event_loop et asyncio.set_event_loop fonctions qui ont résolu ce problème.

Je n'ai pas utilisé AutoBahn mais la bibliothèque WebSockets pypi, et voici ma solution

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()
4
répondu Johan Bjäreholt 2017-05-23 12:34:42

"Existe-t-il même un moyen d'intégrer une boucle d'événement asyncio dans un programme/Tkinter actuellement multithread?"

Oui, exécutez votre programme tkinter avec une boucle d'événement asyncio. La preuve de concept.

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

J'ai expérimenté IDLE avec ces 4 lignes supplémentaires, avec un ralentissement seulement perceptible lorsque la syntaxe met en évidence 1000s de lignes.

2
répondu Terry Jan Reedy 2016-07-24 21:07:22