Python - Running Autobahn|Python asyncio websocket server in a separate subprocess or thread Python - Running Autobahn|Python asyncio websocket server in a separate subprocess or thread tkinter tkinter

Python - Running Autobahn|Python asyncio websocket server in a separate subprocess or thread


First, you're getting 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. 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. 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.

It is possible to start an asyncio event loop in a subprocess started via multiprocessing:

import asynciofrom multiprocessing import Process @asyncio.coroutinedef 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). It should work fine on Windows, or if you use the 'spawn' multiprocessing context.

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. You'll only run into issues if you try to update the GUI from the background thread/process.


The answer by @dano might be correct, but creates an new process which is unnessesary in most situations.

I found this question on Google because i had the same issue myself. I have written an application where i wanted an websocket api to not run on the main thread and this caused your issue.

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.

I didn't use AutoBahn but the pypi websockets library, and here's my solution

import websocketsimport asyncioimport threadingclass 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?"

Yes, run your tkinter program with an asyncio event loop. Proof of concept.

'''Proof of concept integrating asyncio and tk loops.Terry Jan ReedyRun with 'python -i' or from IDLE editor to keep tk window alive.'''import asyncioimport datetime as dtimport tkinter as tkloop = 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.1loop.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.