[英]Python - Running Autobahn|Python asyncio websocket server in a separate subprocess or thread
I have a tkinter based GUI program running in Python 3.4.1. 我有一个基于tkinter的GUI程序在Python 3.4.1中运行。 I have several threads running in the program to get JSON data from various urls.
我在程序中运行了几个线程来从各个URL获取JSON数据。 I am wanting to add some WebSocket functionality to be able to allow program to act as a server and allow several clients to connect to it over a WebSocket and exchange other JSON data.
我想添加一些WebSocket功能,以便能够允许程序充当服务器,并允许多个客户端通过WebSocket连接到它并交换其他JSON数据。
I am attempting to use the Autobahn|Python WebSocket server for asyncio. 我正在尝试使用Autobahn | Python WebSocket服务器进行asyncio。
I first tried to run the asyncio event loop in a separate thread under the GUI program. 我首先尝试在GUI程序下的单独线程中运行asyncio事件循环。 However, every attempt gives 'AssertionError: There is no current event loop in thread 'Thread-1'.
但是,每次尝试都会产生'AssertionError:线程'Thread-1'中没有当前事件循环。
I then tried spawning a process with the standard library multiprocessing package that ran the asyncio event loop in another Process. 然后,我尝试使用标准库多处理程序包生成一个进程,该程序包在另一个进程中运行asyncio事件循环。 When I try this I don't get any exception but the WebSocket server doesn't start either.
当我尝试这个时,我没有得到任何异常,但WebSocket服务器也没有启动。
Is it even possible to run an asyncio event loop in a subprocess from another Python program? 甚至可以在另一个Python程序的子进程中运行asyncio事件循环吗?
Is there even a way to integrate an asyncio event loop into a currently multithreaded/tkinter program? 有没有办法将asyncio事件循环集成到当前多线程/ tkinter程序中?
UPDATE Below is the actual code I am trying to run for an initial test. 更新下面是我尝试运行初始测试的实际代码。
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()
Most of it is straight from the Autobahn|Python example code for asyncio. 其中大部分直接来自asobio的Autobahn | Python示例代码。 If I try to run it as a Process it doesn't do anything, no client can connect to it, if I run netstat -a there is no port 6900 being used.
如果我尝试将它作为进程运行它没有做任何事情,没有客户端可以连接到它,如果我运行netstat -a没有使用端口6900。 If just use start_server() in the main program it creates the WebSocket Server.
如果只在主程序中使用start_server(),则会创建WebSocket服务器。
First, you're getting AssertionError: There is no current event loop in thread 'Thread-1'.
首先,你得到的是
AssertionError: There is no current event loop in thread 'Thread-1'.
because asyncio
requires each thread in your program to have its own event loop, but it will only automatically create an event loop for you in the main thread. 因为
asyncio
要求程序中的每个线程都有自己的事件循环,但它只会在主线程中自动为您创建一个事件循环。 So if you call asyncio.get_event_loop
once in the main thread it will automatically create a loop object and set it as the default for you, but if you call it again in a child thread, you'll get that error. 因此,如果在主线程中调用
asyncio.get_event_loop
一次,它将自动创建一个循环对象并将其设置为默认值,但如果在子线程中再次调用它,则会出现该错误。 Instead, you need to explicitly create/set the event loop when the thread starts: 相反,您需要在线程启动时显式创建/设置事件循环:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
Once you've done that, you should be able to use get_event_loop()
in that specific thread. 完成后,您应该能够在该特定线程中使用
get_event_loop()
。
It is possible to start an asyncio
event loop in a subprocess started via multiprocessing
: 可以在通过
multiprocessing
启动的子asyncio
启动asyncio
事件循环:
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()
Output: 输出:
hi
The only caveat is that if you start an event loop in the parent process as well as the child, you need to explicitly create/set a new event loop in the child if you're on a Unix platform (due to a bug in Python ). 唯一需要注意的是,如果您在父进程和子进程中启动事件循环,则需要在Unix平台上显式创建/设置新的事件循环(由于Python中的错误) )。 It should work fine on Windows, or if you use the 'spawn'
multiprocessing
context. 它应该在Windows上正常工作,或者如果你使用'spawn'
multiprocessing
上下文。
I think it should be possible to start an asyncio
event loop in a background thread (or process) of your Tkinter application and have both the tkinter
and asyncio
event loop run side-by-side. 我认为这应该有可能启动
asyncio
在你的Tkinter应用程序的后台线程(或程序)事件循环,并同时拥有tkinter
和asyncio
事件循环运行并排侧。 You'll only run into issues if you try to update the GUI from the background thread/process. 如果您尝试从后台线程/进程更新GUI,则只会遇到问题。
The answer by @dano might be correct, but creates an new process which is unnessesary in most situations. @dano的答案可能是正确的,但是创建了一个在大多数情况下都是无关紧要的新流程。
I found this question on Google because i had the same issue myself. 我在Google上发现了这个问题,因为我自己也有同样的问题。 I have written an application where i wanted an websocket api to not run on the main thread and this caused your issue.
我写了一个应用程序,我希望websocket api不在主线程上运行,这导致了你的问题。
I found my alternate sollution by simply reading about event loops on the python documentation and found the asyncio.new_event_loop and asyncio.set_event_loop functions which solved this issue. 我通过简单地阅读python文档中的事件循环找到了我的替代sollution,并找到了解决此问题的asyncio.new_event_loop和asyncio.set_event_loop函数。
I didn't use AutoBahn but the pypi websockets library, and here's my solution 我没有使用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()
"Is there even a way to integrate an asyncio event loop into a currently multithreaded/tkinter program?" “有没有办法将asyncio事件循环集成到当前的多线程/ tkinter程序中?”
Yes, run your tkinter program with an asyncio event loop. 是的,使用asyncio事件循环运行您的tkinter程序。 Proof of 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()
I have experimentally run IDLE with those 4 extra lines, with a slowdown only noticeable when syntax highlighting 1000s of lines. 我已经通过实验运行IDLE和那4个额外的行,当语法突出显示1000行时,减速仅显着。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.