Python asyncio.run_coroutine_threadsafe() function (with examples)

Updated: August 10, 2023 By: Khue Post a comment

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 the async keyword.
  • loop: an asyncio event loop object that will run the coroutine. It must be running in another thread, otherwise, the function will raise a RuntimeError.

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!