Подтвердить что ты не робот

Python - запуск Autobahn | сервер Python asyncio websocket в отдельном подпроцессе или потоке

У меня есть GUI-программа на основе tkinter, работающая в Python 3.4.1. У меня есть несколько потоков, запущенных в программе, чтобы получать данные JSON из разных URL-адресов. Я хочу добавить некоторые функции WebSocket, чтобы позволить программе действовать как сервер и разрешать нескольким клиентам подключаться к ней через WebSocket и обмениваться другими данными JSON.

Я пытаюсь использовать сервер Autobahn | Python WebSocket для asyncio.

Сначала я попытался запустить цикл событий asyncio в отдельном потоке в программе GUI. Тем не менее, каждая попытка дает "AssertionError: в потоке" Thread-1 "отсутствует текущий цикл событий.

Затем я попробовал развернуть процесс со стандартным пакетом многопроцессорной библиотеки, который запускал цикл событий asyncio в другом Процессе. Когда я пытаюсь это сделать, я не получаю никаких исключений, но сервер WebSocket также не запускается.

Возможно ли даже запустить цикл событий asyncio в подпроцессе из другой программы Python?

Есть ли способ интегрировать цикл событий asyncio в текущую многопоточную/tkinter-программу?

UPDATE Ниже приведен фактический код, который я пытаюсь запустить для первоначального теста.

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

Большая часть из них прямо из примера кода Autobahn | Python для asyncio. Если я попытаюсь запустить его как процесс, он ничего не сделает, ни один клиент не сможет подключиться к нему, если я запустил netstat -a, не используется порт 6900. Если просто использовать start_server() в основной программе, он создает сервер WebSocket.

4b9b3361

Ответ 1

Во-первых, вы получаете AssertionError: There is no current event loop in thread 'Thread-1'., потому что asyncio требует, чтобы каждый поток в вашей программе имел свой собственный цикл событий, но он будет автоматически создавать цикл событий для вас в основном потоке. Поэтому, если вы вызываете asyncio.get_event_loop один раз в основном потоке, он автоматически создаст объект цикла и установит его как значение по умолчанию для вас, но если вы вызовете его снова в дочернем потоке, вы получите эту ошибку. Вместо этого вам нужно явно создать/установить цикл событий при запуске потока:

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

Как только вы это сделаете, вы сможете использовать get_event_loop() в этом конкретном потоке.

Можно запустить цикл событий asyncio в подпроцессе, запущенном через 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()

Вывод:

hi

Единственное предостережение в том, что если вы запускаете цикл событий в родительском процессе, а также в дочернем, вам нужно явно создать/установить новый цикл событий в дочернем элементе, если вы находитесь на платформе Unix (из-за ошибка в Python). Он должен хорошо работать в Windows, или если вы используете контекст "spawn" multiprocessing.

Я думаю, что должно быть возможно запустить цикл событий asyncio в фоновом потоке (или процессе) вашего приложения Tkinter и иметь одновременно циклы событий tkinter и asyncio бок о бок. Вы столкнетесь с проблемами при попытке обновить графический интерфейс из фонового потока/процесса.

Ответ 2

Ответ @dano может быть правильным, но создает новый процесс, который в большинстве случаев нелогичен.

Я нашел этот вопрос в Google, потому что у меня была такая же проблема. Я написал приложение, в котором я хотел, чтобы aps веб-приложения не запускались в основном потоке, и это вызвало вашу проблему.

Я нашел свое альтернативное решение, просто прочитав о циклах событий в документации на python и нашел функции asyncio.new_event_loop и asyncio.set_event_loop, которые решили эту проблему.

Я не использовал AutoBahn, но библиотеку pypi websockets, а вот мое решение

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

Ответ 3

"Есть ли способ интегрировать цикл событий asyncio в текущую многопоточную/tkinter-программу?"

Да, запустите программу tkinter с помощью цикла событий asyncio. Доказательство концепции.

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

Я экспериментально запускал IDLE с этими 4 дополнительными строками, причем замедление было заметно только тогда, когда синтаксис выделял 1000 строк.