Python run non-blocking async function from sync function
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
Running an async function from synchronous Python code is easy if you are willing to block. Running it without blocking is a different problem. The key distinction is whether the synchronous caller wants the result immediately or just wants to schedule async work and continue.
If you call asyncio.run, the synchronous function blocks until the coroutine finishes. If you truly need non-blocking behavior from sync code, you usually need an already running event loop, often on another thread, and then submit work to that loop.
Blocking Option: asyncio.run
The simplest bridge from sync code to async code is asyncio.run:
This is correct for top-level entry points such as scripts or CLIs. But it is not non-blocking. The sync function waits until the coroutine is done.
What Non-Blocking Really Means Here
A synchronous function cannot both run a coroutine and keep using the same thread freely unless some event loop is already doing the asynchronous work elsewhere. So the usual non-blocking pattern is:
- run an event loop in a background thread
- submit coroutines to that loop
- let the sync caller continue immediately
That gives the synchronous code fire-and-forget or future-based behavior.
Run the Event Loop in a Background Thread
Here is a working example using asyncio.run_coroutine_threadsafe:
The important part is that schedule_job returns immediately after submitting the coroutine. The async work happens on the loop thread.
Fire-and-Forget Versus Waiting for the Result
Once you schedule async work from sync code, you must decide whether the caller will ever wait for the outcome.
If the sync code just wants to trigger background work, it can ignore the returned future. If it may need the result later, keep the future and inspect it when appropriate. That still keeps the initial call non-blocking.
What you should not do is call future.result() immediately if your goal was to avoid blocking. That simply moves the wait to a later line.
Avoid Nesting asyncio.run
A common error is trying to call asyncio.run from code that already lives inside an environment with an active event loop. That fails in notebooks, some web frameworks, and GUI applications.
If an event loop already exists, the better answer is usually:
- if you are already in async code, use
awaitorasyncio.create_task - if you are in sync code outside that loop, submit work to the existing loop thread-safely
The right solution depends on where the loop actually lives.
When asyncio.create_task Is the Right Tool
asyncio.create_task is non-blocking, but it can only be called from code already running inside the event loop thread:
This is useful to understand because many people ask for "run async from sync non-blocking" when the real problem is that they already have async code and should use task scheduling there instead.
Common Pitfalls
- Calling
asyncio.runand expecting non-blocking behavior. - Creating a coroutine object in sync code and never actually scheduling it.
- Calling
future.result()immediately after scheduling and then wondering why the sync caller still blocks. - Trying to use
asyncio.create_taskfrom a thread that does not own the running event loop.
Summary
- '
asyncio.runis the simple sync-to-async bridge, but it blocks.' - Truly non-blocking submission from sync code usually requires an event loop running elsewhere, often in another thread.
- Use
asyncio.run_coroutine_threadsafeto submit work to that loop and continue immediately. - Use
asyncio.create_taskonly when you are already inside async code on the loop thread. - Be explicit about whether the caller needs fire-and-forget behavior or a future result later.
Related reading
- Python synchronous pyaudio data in asynchronous code
- Python threading. How do I lock a thread?
- Python threads all executing on a single core
- Python threads and queue example
- python running coverage on never ending process
- Python Scikit Random Forest Regressor Error
- Python time.sleep vs event.wait
- Python Tornado - Asynchronous Request is blocking
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.