Python - Exécution du serveur Websocket asyncio Autobahn|Python dans un sous-processus ou un thread distinct

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

Question

J'ai un programme GUI basé sur tkinter fonctionnant sous Python 3.4.1.J'ai plusieurs threads en cours d'exécution dans le programme pour obtenir des données JSON à partir de différentes URL.Je souhaite ajouter des fonctionnalités WebSocket pour pouvoir 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 Autobahn|Python WebSocket pour asyncio.

J'ai d'abord essayé d'exécuter la boucle d'événements asyncio dans un thread séparé sous le programme GUI.Cependant, chaque tentative renvoie « AssertionError :Il n'y a pas de boucle d'événements en cours dans le thread « Thread-1 ».

J'ai ensuite essayé de générer un processus avec le package multitraitement de bibliothèque standard qui exécutait la boucle d'événements asyncio dans un autre processus.Lorsque j'essaye cela, je n'obtiens aucune exception mais le serveur WebSocket ne démarre pas non plus.

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

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

MISE À JOURVous trouverez ci-dessous le code 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 majeure partie provient directement de l'exemple de code Autobahn|Python pour asyncio.Si j'essaie de l'exécuter en tant que processus, cela ne fait rien, aucun client ne peut s'y connecter, si j'exécute netstat -a, aucun port 6900 n'est utilisé.Si vous utilisez simplement start_server() dans le programme principal, cela crée le serveur WebSocket.

Était-ce utile?

La solution

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énements, mais il créera automatiquement une boucle d'événements pour vous uniquement dans le thread principal.Alors si tu appelles asyncio.get_event_loop une fois dans le thread principal, il créera automatiquement un objet boucle 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énements au démarrage du thread :

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

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

Il est possible de démarrer un asyncio boucle d'événements 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()

Sortir:

hi

Le seul inconvénient est que si vous démarrez une boucle d'événements dans le processus parent ainsi que dans l'enfant, vous devez explicitement créer/définir une nouvelle boucle d'événements dans l'enfant si vous êtes sur une plate-forme Unix (en raison d'un bug en Python).Cela devrait fonctionner correctement sous Windows, ou si vous utilisez le 'spawn' multiprocessing contexte.

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

Autres conseils

La réponse de @dano pourrait être correcte, mais crée un nouveau processus qui est un désuement dans la plupart des situations.

J'ai trouvé cette question sur Google parce que j'avais le même problème moi-même.J'ai écrit une application où je voulais qu'une API WebSocket ne fonctionne pas sur le fil principal et cela a provoqué votre problème.

J'ai trouvé mon alternative Sollution en lisant simplement des boucles d'événement sur la documentation Python et trouva les fonctions asyncio.new_event_loop et asyncio.set_event_loop qui a résolu ce problème.

Je n'ai pas utilisé Autobahn mais la bibliothèque PYPI WebSockets, 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()

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

Oui, exécutez votre programme Tkinter avec une boucle d'événement Asyncio.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 exécuté expérimentalement au ralenti avec ces 4 lignes supplémentaires, avec un ralentissement visible uniquement lorsque la syntaxe met en évidence les 1000 secondes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top