I'm trying to create two threads that each have their own asyncio
event loop.
I've tried the following code but it doesn't seem to work:
import asyncio
from threading import Thread
def hello(thread_name):
print('hello from thread {}!'.format(thread_name))
event_loop_a = asyncio.new_event_loop()
event_loop_b = asyncio.new_event_loop()
def callback_a():
asyncio.set_event_loop(event_loop_a)
asyncio.get_event_loop().call_soon_threadsafe(lambda: hello('a'))
def callback_b():
asyncio.set_event_loop(event_loop_b)
asyncio.get_event_loop().call_soon_threadsafe(lambda: hello('b'))
thread_a = Thread(target=callback_a, daemon=True)
thread_b = Thread(target=callback_b, daemon=True)
thread_a.start()
thread_b.start()
My use case is calling Tornado web framework's websocket_connect
async function.
Your threads are queuing callbacks in their respective event loops, but they exit before actually running the event loop, so the callbacks never get executed. Also, you don't need call_soon_threadsafe
, since you are invoking the callback from the same thread the event loop is (or, rather, will be) running on.
This code prints the expected output:
import asyncio
from threading import Thread
def hello(thread_name):
print('hello from thread {}!'.format(thread_name))
event_loop_a = asyncio.new_event_loop()
event_loop_b = asyncio.new_event_loop()
def callback_a():
asyncio.set_event_loop(event_loop_a)
asyncio.get_event_loop().call_soon(lambda: hello('a'))
event_loop_a.run_forever()
def callback_b():
asyncio.set_event_loop(event_loop_b)
asyncio.get_event_loop().call_soon(lambda: hello('b'))
event_loop_b.run_forever()
thread_a = Thread(target=callback_a, daemon=True)
thread_b = Thread(target=callback_b, daemon=True)
thread_a.start()
thread_b.start()
A more typical use case for call_soon_threadsafe
, and more in line with what you might have had in mind, is to submit a callback (or a coroutine using asyncio.run_coroutine_threadsafe
) to an event loop running in another thread. Here is an example:
import asyncio, threading
def hello(thread_name):
print('hello from thread {}!'.format(thread_name))
event_loop_a = asyncio.new_event_loop()
event_loop_b = asyncio.new_event_loop()
def run_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
threading.Thread(target=lambda: run_loop(event_loop_a)).start()
threading.Thread(target=lambda: run_loop(event_loop_b)).start()
event_loop_a.call_soon_threadsafe(lambda: hello('a'))
event_loop_b.call_soon_threadsafe(lambda: hello('b'))
event_loop_a.call_soon_threadsafe(event_loop_a.stop)
event_loop_b.call_soon_threadsafe(event_loop_b.stop)
In that case it is rarely needed to have more than one event loop thread - you'd typically create only one, and allow that thread to service all your asyncio needs. After all, being able to host a large number of tasks in a single event loop is one of the strong points of asyncio.