Only show guides for your app
Python's async model in plain terms — how discord.py and async scrapers do many things at once on one thread, and the one blocking call that freezes all of it.
asyncio is how Python handles lots of slow, waiting-on-the-network work without threads: it starts many operations, and while each waits for a reply, others run. It's the engine under discord.py (every command handler is async) and the fastest way to write a scraper that fetches hundreds of pages. This guide covers the async patterns that fit those Falix workloads, and the single mistake that stalls the whole thing.
| At a glance | |
|---|---|
| You need | A Falix server running the Python application |
| Plan | Any |
| Time | Twenty minutes |
| Good to already know | Python basics; a running bot or script helps |
async def, await, and the loopAn async def function is a coroutine — calling it doesn't run it, it hands back something you await. await pauses the coroutine until the result is ready, freeing the single thread to run other coroutines meanwhile. An event loop drives them all; asyncio.run() starts one:
import asyncio
async def fetch(name):
await asyncio.sleep(1) # stands in for a network call
return f"done: {name}"
async def main():
result = await fetch("one")
print(result, flush=True)
asyncio.run(main())
🎯 Good to know: On Falix, print with
flush=True(orprint(..., flush=True)) for long-running async apps, so your logs reach the Console immediately instead of sitting in Python's buffer.
In discord.py you don't call asyncio.run yourself — the library runs the loop. Your job is to keep handlers async and await the bot's calls:
@bot.tree.command(name="ping", description="Check the bot")
async def ping(interaction: discord.Interaction):
await interaction.response.send_message("Pong!")
The rule that keeps a bot healthy: never block the loop. A blocking call inside a handler freezes the entire bot — it stops answering commands and can drop its Discord connection. See the trap below.
Doing requests one after another wastes almost all your time waiting. asyncio.gather runs them concurrently and collects the results:
import asyncio, aiohttp
async def get(session, url):
async with session.get(url) as resp:
return await resp.text()
async def main():
urls = ["https://example.com", "https://example.org", "https://example.net"]
async with aiohttp.ClientSession() as session:
pages = await asyncio.gather(*(get(session, u) for u in urls))
print(f"fetched {len(pages)} pages", flush=True)
asyncio.run(main())
Ten sequential 1-second requests take ten seconds; ten with gather take about one. (aiohttp or httpx are the async HTTP clients — add one on the Packages page.)
💡 Tip: Don't fire thousands of requests at once — you'll hammer the target and hit rate limits. Cap concurrency with an
asyncio.Semaphore(20)andawaitit inside each task.
Because it's all one thread, a synchronous call that blocks stops every other coroutine dead. The three usual offenders:
| Blocking call | Why it hurts | Use instead |
|---|---|---|
time.sleep(5) |
Freezes the loop for 5s | await asyncio.sleep(5) |
requests.get(...) |
Synchronous HTTP blocks the loop | aiohttp / httpx (async) |
| Heavy CPU work | Nothing else runs while it churns | await asyncio.to_thread(fn, ...) |
If your bot periodically goes unresponsive with no error in the console, a blocking call in an async handler is the first suspect. asyncio.to_thread(...) pushes a blocking or CPU-heavy function onto a worker thread so the loop keeps turning.
asyncio.create_task(...) starts a coroutine running in the background without waiting for it. Two honest cautions:
try/except and log failures, or the task dies quietly.task = asyncio.create_task(background_job())
# ... keep `task` alive; later:
await task # surfaces any exception it raised
discord.ext.tasks wraps this pattern for bots (looping jobs like periodic announcements) and handles the reference-keeping for you.
time.sleep/requests for their async equivalents, or offload with asyncio.to_thread.RuntimeWarning: coroutine '...' was never awaited — you called an async function without await. Add the await, or create_task it if it should run in the background.asyncio.run() cannot be called from a running event loop — you're already inside one (e.g. discord.py runs it). Don't call asyncio.run there; just await your coroutine.The asyncio model has a lot more depth (queues, cancellation, timeouts) — the official Python asyncio docs are the reference.