简体   繁体   English

Python - 在一个单独的子进程或线程中运行Autobahn | Python asyncio websocket服务器

[英]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应用程序的后台线程(或程序)事件循环,并同时拥有tkinterasyncio事件循环运行并排侧。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM