Python - Запуск Autobahn|Python asyncio websocket server в отдельном подпроцессе или потоке
У меня есть программа GUI на основе tkinter, работающая на Python 3.4.1. У меня в программе запущено несколько потоков для получения данных JSON из разных URL-адресов. Я хочу добавить некоторые функциональные возможности WebSocket, чтобы позволить программе действовать в качестве сервера и позволить нескольким клиентам подключаться к ней через WebSocket и обмениваться другими данными JSON.
Я пытаюсь использовать сервер Autobahn|Python WebSocket для asyncio.
Сначала я попытался запустить цикл событий asyncio в отдельном потоке в программе с графическим интерфейсом. Однако каждая попытка выдает 'AssertionError: в потоке'Thread-1'нет текущего цикла событий.
Затем я попытался порождать процесс с помощью пакета многопроцессорной обработки стандартной библиотеки, который запускал цикл событий asyncio в другом процессе. Когда я пытаюсь это сделать, я не получаю никаких исключений, но сервер WebSocket тоже не запускается.
Возможно ли вообще запустить цикл событий asyncio в подпроцессе из другой программы Python?
Есть ли способ интегрировать цикл событий asyncio в многопоточную программу /tkinter?
ОБНОВЛЕНИЕ Ниже приведен фактический код, который я пытаюсь запустить для начального теста.
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.
3 ответа
Во-первых, вы получаете 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
Цикл событий запускается бок о бок. Вы столкнетесь с проблемами, только если попытаетесь обновить графический интерфейс из фонового потока / процесса.
Ответ dano может быть правильным, но он создает новый процесс, который в большинстве случаев является неестественным.
Я нашел этот вопрос в Google, потому что у меня была такая же проблема. Я написал приложение, в котором хотел, чтобы API веб-сокета не запускался в главном потоке, и это вызвало вашу проблему.
Я нашел свое альтернативное решение, просто прочитав о циклах событий в документации по 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()
"Есть ли способ интегрировать цикл событий 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 дополнительными строками, причем замедление заметно только при синтаксисе, выделяющем тысячи строк.