Comparison with Alternative Solutions

Date: 2025-11-10 Status: Analysis of existing solutions


Overview

This document compares SmartAsync with existing libraries and approaches for handling sync/async interoperability in Python.

Existing Solutions

1. asyncio.to_thread() (Python Stdlib 3.9+)

What it does: Runs synchronous functions in a thread pool from async context.

import asyncio

async def my_async_function():
    result = await asyncio.to_thread(blocking_sync_call)
    return result

Pros:

  • ✅ Part of Python standard library (no dependencies)

  • ✅ Well-tested and maintained

  • ✅ Simple API

Cons:

  • ❌ Only one direction: sync → async context

  • ❌ Not automatic - must call explicitly

  • ❌ Doesn’t handle async → sync context

  • ❌ No auto-detection of context

Use case: When you know you’re in async context and need to call blocking code.


2. asgiref.sync (Django Framework)

What it does: Provides sync_to_async() and async_to_sync() decorators for Django.

from asgiref.sync import sync_to_async, async_to_sync

@sync_to_async
def sync_database_query():
    return MyModel.objects.get(id=1)

@async_to_sync
async def async_api_call():
    async with httpx.AsyncClient() as client:
        return await client.get(url)

Pros:

  • ✅ Bidirectional (both sync→async and async→sync)

  • ✅ Battle-tested in Django ecosystem

  • ✅ Supports thread-sensitive operations

  • ✅ Configuration options (thread_sensitive, executor)

Cons:

  • ❌ Requires explicit decorator choice (two different decorators)

  • ❌ No auto-detection of context

  • ❌ Additional dependency

  • ❌ Heavier (designed for framework integration)

  • ❌ Must decide direction at decoration time

Use case: Django applications mixing sync and async views/middleware.

GitHub: https://github.com/django/asgiref


3. anyio (Trio-compatible abstraction)

What it does: Abstraction layer over asyncio and Trio.

import anyio

async def my_async():
    result = await anyio.to_thread.run_sync(blocking_func)
    return result

Pros:

  • ✅ Works with both asyncio and Trio

  • ✅ Rich ecosystem of utilities

  • ✅ Thread and process pools

Cons:

  • ❌ Only sync → async direction

  • ❌ No auto-detection

  • ❌ Heavy dependency (abstraction layer)

  • ❌ More complex API (by design - more features)

  • ❌ Overkill if you only need asyncio

Use case: Applications that need to support both asyncio and Trio, or need the full anyio feature set.

GitHub: https://github.com/agronholm/anyio


4. asyncer (by Sebastián Ramírez - FastAPI author)

What it does: Simplified wrappers for sync/async conversion.

from asyncer import asyncify, syncify

# Convert sync to async
async_version = asyncify(sync_function)
result = await async_version()

# Convert async to sync
sync_version = syncify(async_function)
result = sync_version()

Pros:

  • ✅ Bidirectional

  • ✅ Simple, clean API

  • ✅ Lightweight

  • ✅ Good documentation

Cons:

  • ❌ No auto-detection of context

  • ❌ Requires explicit wrapper creation

  • ❌ Must know direction at wrapping time

  • ❌ Additional dependency

Use case: When you want simple sync↔async conversion and don’t need auto-detection.

GitHub: https://github.com/tiangolo/asyncer


5. nest_asyncio

What it does: Patches asyncio to allow nested event loops.

import nest_asyncio
nest_asyncio.apply()

# Now you can call asyncio.run() from within an async context

Pros:

  • ✅ Allows nested event loops

  • ✅ Can solve some edge cases

Cons:

  • ❌ Monkey-patches asyncio internals

  • ❌ Not recommended for production

  • ❌ Can cause subtle bugs

  • ❌ Compatibility issues with libraries

  • ❌ Hacky approach

Use case: Jupyter notebooks, interactive environments. Not recommended for production.

GitHub: https://github.com/erdewit/nest_asyncio


Feature Comparison Matrix

Feature

SmartAsync

asgiref

anyio

asyncer

stdlib (to_thread)

Auto-detection

✅ Yes

❌ No

❌ No

❌ No

❌ No

Bidirectional

✅ Yes

✅ Yes

❌ No

✅ Yes

❌ No

Single decorator

✅ Yes

❌ No (2 decorators)

❌ No

❌ No (2 functions)

N/A

Zero dependencies

✅ Yes

❌ No

❌ No

❌ No

✅ Yes

Async → Sync

✅ asyncio.run

✅ async_to_sync

❌ No

✅ syncify

✅ asyncio.run

Sync → Async

✅ to_thread

✅ sync_to_async

✅ to_thread

✅ asyncify

✅ to_thread

Pattern matching

✅ Yes (Python 3.10+)

❌ No

❌ No

❌ No

N/A

Asymmetric cache

✅ Yes

❌ No

❌ No

❌ No

N/A

Thread pool config

❌ Uses default

✅ Configurable

✅ Configurable

❌ Uses default

❌ Uses default

Trio support

❌ asyncio only

❌ asyncio only

✅ Yes

❌ asyncio only

❌ asyncio only

Python version

3.10+ (match)

3.8+

3.8+

3.8+

3.9+ (to_thread)


What Makes SmartAsync Unique

1. Automatic Context Detection

SmartAsync - Same code works everywhere:

@smartasync
async def fetch_data(url: str):
    async with httpx.AsyncClient() as client:
        return await client.get(url).json()

# Sync context - auto-detected
data = fetch_data(url)  # No await needed!

# Async context - auto-detected
data = await fetch_data(url)  # Normal await

Others - Must decide direction upfront:

# asgiref - two decorators
@sync_to_async
def fetch_sync(url: str): ...

@async_to_sync
async def fetch_async(url: str): ...

# asyncer - explicit wrapper
async_fetch = asyncify(sync_fetch)
sync_fetch = syncify(async_fetch)

2. Single Decorator for Both Directions

SmartAsync:

@smartasync
async def async_method(): ...

@smartasync
def sync_method(): ...

# Both work in both contexts!

Others:

# asgiref - different decorators
@sync_to_async
def method1(): ...

@async_to_sync
async def method2(): ...

# asyncer - different wrappers
async_version = asyncify(sync_method)
sync_version = syncify(async_method)

3. Zero Configuration

SmartAsync:

@smartasync
async def method(): ...
# Done! Works everywhere.

asgiref:

@sync_to_async(thread_sensitive=False, executor=custom_executor)
def method(): ...
# Must configure options

4. Pattern Matching Clarity

SmartAsync uses Python 3.10+ pattern matching for clear dispatch:

match (async_context, async_method):
    case (False, True):  # Sync context + Async method
        return asyncio.run(coro)
    case (False, False): # Sync context + Sync method
        return method(self, *args, **kwargs)
    case (True, True):   # Async context + Async method
        return coro
    case (True, False):  # Async context + Sync method
        return asyncio.to_thread(method, self, *args, **kwargs)

Each case is explicitly documented and clear.


Unique Use Cases

1. Testing Simplification

With SmartAsync:

class Service:
    @smartasync
    async def process(self, data): ...

# Sync test (no event loop setup!)
def test_process():
    svc = Service()
    result = svc.process(data)  # Just works!
    assert result["status"] == "ok"

# Async test (natural)
async def test_process_async():
    svc = Service()
    result = await svc.process(data)
    assert result["status"] == "ok"

With other libraries:

# Must create separate sync/async versions
# Or setup event loop in sync tests

import asyncio

def test_process():
    svc = Service()
    result = asyncio.run(svc.process(data))  # Manual asyncio.run
    assert result["status"] == "ok"

2. Unified Library API

With SmartAsync - One implementation for all users:

class UnifiedAPI:
    """Library with single implementation."""

    @smartasync
    async def fetch(self, url: str):
        async with httpx.AsyncClient() as client:
            return await client.get(url).json()

# Sync user
api = UnifiedAPI()
data = api.fetch(url)  # No await

# Async user
api = UnifiedAPI()
data = await api.fetch(url)  # With await

With other libraries - Must maintain two versions or force users to one approach.

3. Gradual Migration

With SmartAsync:

class LegacySystem:
    @smartasync
    def old_sync_method(self):
        """Legacy blocking code."""
        return db.query_sync()

    @smartasync
    async def new_async_method(self):
        """Modern async code."""
        return await db.query_async()

# Both work in both contexts - no rewrite needed!

Migration can happen incrementally without breaking changes.


When NOT to Use SmartAsync

1. Need Fine-Grained Control

If you need custom thread pools, specific executors, or thread-sensitive operations:

  • Use asgiref - Provides configuration options

  • Use anyio - Full control over execution

2. Trio Support Required

If your application uses Trio instead of asyncio:

  • Use anyio - Works with both asyncio and Trio

3. Nested Event Loops

If you need nested event loops (e.g., Jupyter notebooks):

  • ⚠️ Use nest_asyncio (with caution!)

  • Better: Restructure code to avoid nesting

4. Python < 3.10

SmartAsync requires Python 3.10+ for pattern matching:

  • Use asgiref or asyncer (Python 3.8+)

  • Use stdlib asyncio.to_thread() (Python 3.9+)

5. Framework Integration

If you’re building Django apps:

  • Use asgiref - Designed for Django integration


Performance Comparison

Overhead Analysis

Operation

SmartAsync

asgiref

anyio

asyncer

stdlib

Async call (cached)

~1.3μs

~2μs

~3μs

~2μs

N/A

Async call (first)

~2.3μs

~3μs

~4μs

~3μs

N/A

Sync → Async

~102μs

~110μs

~105μs

~100μs

~100μs

Async → Sync (thread)

~50-100μs

~60-120μs

~55-110μs

~50-100μs

~50-100μs

Conclusion: All solutions have similar performance - overhead dominated by asyncio.run() and thread creation, not the wrapper logic.


Recommendations

Use SmartAsync When:

  • ✅ You want automatic context detection

  • ✅ You need one decorator for both directions

  • ✅ You want zero configuration

  • ✅ You prefer simple, clean code

  • ✅ You’re using Python 3.10+

  • ✅ You want zero dependencies (stdlib only)

Use asgiref When:

  • ✅ Building Django applications

  • ✅ Need thread-sensitive operations

  • ✅ Need custom executors

  • ✅ Want production-tested framework integration

Use anyio When:

  • ✅ Need Trio support

  • ✅ Building library that works with multiple async frameworks

  • ✅ Need the full anyio feature set (sockets, processes, etc.)

Use asyncer When:

  • ✅ Want simple wrappers without auto-detection

  • ✅ Prefer explicit over implicit

  • ✅ Like the FastAPI ecosystem style

Use stdlib (asyncio.to_thread) When:

  • ✅ Only need sync → async direction

  • ✅ Want absolute minimal dependencies

  • ✅ Have simple use case (no need for wrapper)


Summary

SmartAsync fills a unique niche: No other library offers automatic bidirectional context detection with a single decorator.

Library

Philosophy

Best For

SmartAsync

“Do What I Mean” - Auto-detection

Unified APIs, testing, gradual migration

asgiref

“Framework Integration” - Configuration

Django apps, production frameworks

anyio

“Cross-Framework” - Abstraction

Trio support, cross-framework libs

asyncer

“Explicit Wrappers” - Simplicity

FastAPI-style explicit conversions

stdlib

“Minimal” - One direction only

Simple async-to-sync threading

The Verdict: If you want the simplest possible way to make code work in both sync and async contexts without thinking about it, SmartAsync is the right choice.


References

  • asgiref: https://github.com/django/asgiref

  • anyio: https://github.com/agronholm/anyio

  • asyncer: https://github.com/tiangolo/asyncer

  • nest_asyncio: https://github.com/erdewit/nest_asyncio

  • asyncio.to_thread: https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread


Last Updated: 2025-11-10 Maintainer: SmartAsync Team