This succinct, example-based article is about the asyncio.run_coroutine_threadsafe()
function in Python.
The fundamentals
asyncio.run_coroutine_threadsafe()
was added to the Python programming language in version 3.4.4. The function is used to submit a coroutine to a given event loop from another thread. It allows regular blocking code to make use of asyncio features, or to call blocking functions that have no async equivalent from the asyncio
module.
Syntax & parameters
The syntax of the asyncio.run_coroutine_threadsafe()
function is:
asyncio.run_coroutine_threadsafe(coro, loop)
Where:
coro
: a coroutine object that will be executed in the event loop. It must be an awaitable object, such as a coroutine declared with theasync
keyword.loop
: an asyncio event loop object that will run the coroutine. It must be running in another thread, otherwise, the function will raise aRuntimeError
.
The asyncio.run_coroutine_threadsafe()
function returns a concurrent.futures.Future
object that represents the result of the coroutine submitted to the event loop. This object is a blocking future, which means that you can call its result()
method to wait for the coroutine to finish and get its return value. However, you should be careful not to call this method from the same thread as the event loop, as this will cause a deadlock. You can also use other methods of the Future
object, such as add_done_callback()
, cancel()
, or exception()
to interact with the coroutine.
When and when NOT to use asyncio.run_coroutine_threadsafe()
The asyncio.run_coroutine_threadsafe()
function is really helpful in some situations:
- When you are introducing
asyncio
into an existing large program (likely an old one) that uses threads and blocking calls and cannot be converted to asyncio all at once. - When you need to call blocking functions that have no async equivalent from
asyncio
, e.g. CPU-bound functions, legacy database drivers, and something like that.
If you’re starting a new project (that doesn’t involve blocking functions without async equivalent), there is no reason to use the asyncio.run_coroutine_threadsafe()
function. Instead, it’s better to write the code entirely using asyncio features, such as coroutines, tasks, futures, and async/await
syntax.
At this point, you might get bored with concepts and boring words. It’s time to get our hands dirty with code.
Examples
Example 1: Welcome to Sling Academy (basic)
This simple example shows you how to use the asyncio.run_coroutine_threadsafe()
function to print “Welcome to Sling Academy!” from a coroutine in another thread:
# SlingAcademy.com
# This code uses Python 3.11.4
import asyncio
import threading
# Define a coroutine
async def welcome():
await asyncio.sleep(2)
print("Welcome to Sling Academy!")
return "This is the result of the coroutine."
# Define a function that runs the coroutine in a thread
def run_in_thread(loop):
# Submit the coroutine to the loop
future = asyncio.run_coroutine_threadsafe(welcome(), loop)
# Wait for the result
print(f"Result: {future.result()}")
if __name__ == "__main__":
# Get the main event loop
loop = asyncio.get_event_loop()
# Create a thread that runs the coroutine
thread = threading.Thread(target=run_in_thread, args=(loop,))
# Start the thread
thread.start()
# Run the loop forever
loop.run_forever()
Output:
Welcome to Sling Academy!
Result: This is the result of the coroutine.
Example 2: Fetching URLs (intermediate)
In this example, we’ll use the asyncio.run_coroutine_threadsafe()
function to fetch multiple URLs concurrently from a coroutine in another thread. We’ll use a library named requests
to make network requests. You can install it by running:
pip install requests
The code:
# SlingAcademy.com
# This code uses Python 3.11.4
import asyncio
import requests
import threading
async def fetch_url(url):
# Use requests library to get the content of the url
response = requests.get(url)
return response.content
def run_in_thread(loop):
# A list of urls to fetch
urls = [
"https://api.slingacademy.com",
"https://api.slingacademy.com/v1/sample-data/photos",
"https://api.slingacademy.com/v1/sample-data/users",
]
# A list of futures to store the results
futures = []
# Submit each coroutine to the loop
for url in urls:
future = asyncio.run_coroutine_threadsafe(fetch_url(url), loop)
futures.append(future)
# Wait for all futures to complete
for future in futures:
# Print the length of each content
print(len(future.result()))
if __name__ == "__main__":
# Get the main event loop
loop = asyncio.get_event_loop()
# Create a thread that runs the coroutine
thread = threading.Thread(target=run_in_thread, args=(loop,))
# Start the thread
thread.start()
# Run the loop forever
loop.run_forever()
Output:
64
2569
3682
Example 3: Calling CPU-bound functions (advanced)
This example demonstrates how to use the asyncio.run_coroutine_threadsafe()
function to call a CPU-bound function (factorial) from a coroutine in another thread:
# SlingAcademy.com
# This code uses Python 3.11.4
import asyncio
import concurrent.futures
import math
import threading
async def factorial(n):
# Use concurrent.futures module to run the math.factorial function in a separate process
with concurrent.futures.ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(pool, math.factorial, n)
return result
def run_in_thread(loop):
# A list of numbers to calculate the factorial
numbers = [5, 10, 20, 30]
# A list of futures to store the results
futures = []
# Submit each coroutine to the loop
for n in numbers:
future = asyncio.run_coroutine_threadsafe(factorial(n), loop)
futures.append(future)
# Wait for all futures to complete
for future in futures:
# Print the result of each factorial
print(future.result())
if __name__ == "__main__":
# Get the main event loop
loop = asyncio.get_event_loop()
# Create a thread that runs the coroutine
thread = threading.Thread(target=run_in_thread, args=(loop,))
# Start the thread
thread.start()
# Run the loop forever
loop.run_forever()
Output:
120
3628800
2432902008176640000
265252859812191058636308480000000
In this case, the asyncio.run_coroutine_threadsafe()
function helps us perform a computation-intensive task that would block the event loop if done in the same thread.
Afterword
You’ve learned the fundamentals of the asyncio.run_coroutine_threadsafe()
function in Python and seen its helpfulness in certain situations. Try to run all of the examples on your machine, modify some lines of code, and see what happens. This is a nice way to learn new things.
This tutorial ends here. If you find something outdated or incorrect, please let me know by leaving a comment. Happy coding & have a nice day!