Making Asynchronous Code Synchronous in aiohttp

Feb 22, 2024 ยท 3 min read

The aiohttp library is popular for making asynchronous HTTP requests in Python. Its async/await syntax helps you write non-blocking code that can handle many requests concurrently.

However, sometimes you need to integrate aiohttp with synchronous code or external libraries. This article covers different techniques to bridge the gap:

The run_in_executor() Method

The easiest way is using run_in_executor(), which runs a coroutine in a thread or process pool:

import aiohttp
from concurrent.futures import ThreadPoolExecutor

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

def sync_fetch(url):
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(
        aiohttp.ClientSession().run_in_executor(ThreadPoolExecutor(), fetch, url))
    return result

This avoids blocking the event loop thread while running synchronous code. The downside is managing the thread/process pools yourself.

The asyncio.to_thread() Function

to_thread() is an alternative that uses a default thread pool:

import asyncio
import aiohttp

async def fetch(url):
    async with aiohttp.request('GET', url) as response: 
        return await response.text()

def sync_fetch(url):
    return asyncio.get_event_loop().run_until_complete(
        asyncio.to_thread(fetch, url))  

Less code, but you have less control over the thread pool. Useful for simple cases.

Running an Event Loop in a Thread

You can run the entire asyncio event loop in a background thread:

import asyncio
import threading 

async def main():
    async with aiohttp.request('GET', url) as resp:
        print(await resp.text())

thread = threading.Thread(target=asyncio.run, args=(main(),))
thread.start()
thread.join()

This keeps async code isolated and non-blocking. But uses more resources as the event loop runs separately.

The nest_asyncio Decorator (Python 3.7+)

The nest_asyncio decorator integrates asyncio with other event loops:

import nest_asyncio
nest_asyncio.apply() 

import aiohttp

def sync_fetch(url):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop) 
    result = loop.run_until_complete(aiohttp.request('GET', url))
    return result.text()

This allows running asyncio code synchronously by creating a new event loop. But has downsides around resource usage.

Key Takeaways

  • Use run_in_executor() for integrating small async parts in synchronous code.
  • For bigger async integrations, run the event loop in a thread.
  • The nest_asyncio decorator can run asyncio synchronously but has tradeoffs.
  • Understand if you need full async integration or just a sync interface to async code.
  • The ideal approach depends on your specific use case - whether you want to call async code from a sync program or vice versa. Measure resource usage and performance to pick the right method.

    Browse by tags:

    Browse by language:

    The easiest way to do Web Scraping

    Get HTML from any page with a simple API call. We handle proxy rotation, browser identities, automatic retries, CAPTCHAs, JavaScript rendering, etc automatically for you


    Try ProxiesAPI for free

    curl "http://api.proxiesapi.com/?key=API_KEY&url=https://example.com"

    <!doctype html>
    <html>
    <head>
        <title>Example Domain</title>
        <meta charset="utf-8" />
        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
    ...

    X

    Don't leave just yet!

    Enter your email below to claim your free API key: