Inhoud
Python Asyncio/Threading
ποΈ Terug naar start
β ποΈ Python Start
π π Nest Asyncioin streamlit
Threading en asyncio (soms met nest_asyncio) worden allebei gebruikt om meerdere dingen tegelijk te laten gebeuren. Maar ze werken op totaal verschillende manieren.
Threading
De module threading in Python gebruik je om meerdere dingen tegelijk te laten gebeuren binnen één programma. Dat heet multithreading. Dit is vooral handig wanneer je programma veel wacht op iets, zoals op:
- een bestand dat wordt ingelezen of geschreven;
- een netwerkverzoek (zoals een API-aanroep);
- een gebruiker die iets moet invoeren;
- een time.sleep() of wachttijd;
- langzame hardware zoals seriΓ«le communicatie (bijv. met een sensor of Arduino).
π§ Wanneer gebruik je threading?
Gebruik threading:
- als je niet intensief rekent, maar juist wacht op dingen;
- als je wil dat je programma niet vastloopt tijdens een wachttijd;
- als je een achtergrondtaak wil laten lopen terwijl je hoofdprogramma doorgaat.
π§ Simpele uitleg met analogie
Stel: je bent een kok in een keuken. Je zet water op voor pasta (dat duurt 10 minuten). In plaats van 10 minuten te wachten, begin je alvast groenten te snijden. Je doet dus meerdere taken tegelijk.
Zonder threading wacht je tot het water kookt, en doe je pas daarna het snijwerk.
β Voorbeeld 1: taak op de achtergrond
import threading
import time
def achtergrondtaak():
for i in range(5):
print("Achtergrondtaak draait...", i)
time.sleep(1)
# Start een thread
thread = threading.Thread(target=achtergrondtaak)
thread.start()
# Hoofdprogramma gaat door
for i in range(3):
print("Hoofdprogramma doet iets anders...", i)
time.sleep(1)
# Wacht tot de thread klaar is
thread.join()
print("Alles klaar!")
Wat gebeurt hier?
- De achtergrondtaak() draait in een aparte thread;
- Het hoofdprogramma doet ondertussen iets anders;
- thread.join() zorgt dat we wachten tot de thread klaar is.
β Voorbeeld 2: twee dingen tegelijk
def print_hello():
for _ in range(3):
print("Hallo!")
time.sleep(1)
def print_world():
for _ in range(3):
print("Wereld!")
time.sleep(1)
t1 = threading.Thread(target=print_hello)
t2 = threading.Thread(target=print_world)
t1.start()
t2.start()
t1.join()
t2.join()
Je krijgt dan een mix van βHallo!β en βWereld!β, afhankelijk van de timing.
β οΈ Belangrijk om te weten
- Let op gedeelde variabelen
- Threads kunnen tegelijk aan dezelfde data zitten. Gebruik dan threading.Lock() om problemen (race conditions) te voorkomen.
- Niet geschikt voor zware berekeningen
- Door de Global Interpreter Lock (GIL) in CPython is threading niet geschikt voor zware rekenklussen. Gebruik dan liever multiprocessing.
π§ Waar kun je threading voor inzetten?
Hier zijn wat praktische toepassingen:
- Een GUI app waarbij je netwerkverkeer doet zonder dat het scherm βbevriestβ;
- Een webscraper die meerdere pagina's tegelijk ophaalt;
- Een game waarin vijanden op de achtergrond bewegen;
- Een server die meerdere clients tegelijk moet afhandelen;
- Een sensorapp (bijv. via seriΓ«le poort) die data blijft uitlezen terwijl het hoofdprogramma doorgaat.
Loader
Je kunt threading prima gebruiken om een loader of wachtschermpje te laten draaien terwijl je AI-chatbot bezig is met denken of antwoorden ophalen. Zo lijkt je app sneller en reageert deze beter op de gebruiker.
β Voorbeeld: loader tijdens AI-berekening
Stel: je AI-bot doet iets tijdrovends, zoals een OpenAI-aanroep. Je wilt intussen een draaiende βloaderβ weergeven.
import threading
import time
import sys
def loader(stop_event):
chars = "|/-\\"
i = 0
while not stop_event.is_set():
print(f"\rWachten op antwoord... {chars[i % len(chars)]}", end="", flush=True)
i += 1
time.sleep(0.1)
print("\rAntwoord ontvangen! ")
def ai_antwoord():
# Simuleert een trage AI-actie, bv. OpenAI API-call
time.sleep(5)
return "Dit is het AI-antwoord."
# Event om de loader te stoppen
stop_event = threading.Event()
loader_thread = threading.Thread(target=loader, args=(stop_event,))
loader_thread.start()
# Voer AI-actie uit
antwoord = ai_antwoord()
# Stop de loader
stop_event.set()
loader_thread.join()
# Toon antwoord
print(antwoord)
π‘ Wat gebeurt hier?
- De loader() draait in een aparte thread en laat een draaiende animatie zien.
- Zodra ai_antwoord() klaar is, wordt het stop_event geactiveerd.
- Dan stopt de loader en laat je het echte antwoord zien.
Asyncio
asyncio is een ingebouwde module in Python waarmee je asynchrone (concurrente) code kunt schrijven. Het maakt gebruik van coroutines, die met het sleutelwoord async worden gedefinieerd, en van het await-statement om te wachten op asynchrone operaties (zoals I/O-bound taken) zonder de hele applicatie te blokkeren. Met een event loop worden deze coroutines efficiΓ«nt gepland en uitgevoerd. Dit is bijzonder handig voor toepassingen waar je veel wachttijd hebt, zoals bij netwerkcommunicatie of bestand I/O, omdat je zo meerdere taken tegelijk kunt laten draaien.
import asyncio
class AsyncWorker:
def __init__(self, name):
self.name = name
async def async_task(self, seconds):
print(f"{self.name}: Taak gestart, wacht {seconds} seconden.")
# Simuleer een I/O-bound operatie met asyncio.sleep
await asyncio.sleep(seconds)
print(f"{self.name}: Taak voltooid na {seconds} seconden.")
return f"{self.name} klaar na {seconds} seconden"
async def run_tasks(self):
print(f"{self.name}: Start meerdere taken...")
# Start twee asynchrone taken en wacht tot beide klaar zijn
results = await asyncio.gather(
self.async_task(2),
self.async_task(3)
)
print(f"{self.name}: Alle taken voltooid. Resultaten: {results}")
# Het uitvoeren van de asynchrone taken via de event loop
if __name__ == "__main__":
worker = AsyncWorker("Worker1")
asyncio.run(worker.run_tasks())
Uitleg van de code:
- De class AsyncWorker:
- De
__init__
-methode initialiseert een instantie met een naam.
- async_task: Dit is een coroutine die een taak simuleert door een aantal seconden te wachten met await asyncio.sleep(seconds).
- run_tasks: Deze coroutine start meerdere taken tegelijk met asyncio.gather en wacht totdat ze allebei voltooid zijn.
- Event loop starten:
- Met asyncio.run(worker.run_tasks()) wordt de event loop gestart en de coroutine run_tasks uitgevoerd.
Dit voorbeeld laat zien hoe je asyncio kunt gebruiken binnen een class om meerdere asynchrone taken tegelijk te laten draaien. Hierdoor kun je efficiΓ«nter omgaan met taken die wachten op externe bronnen zonder dat je applicatie blokkeert.
import asyncio
class AsyncApp:
def __init__(self, name):
self.name = name
# Start de asynchrone taken direct bij initialisatie
self.start()
async def async_task(self, seconds):
print(f"{self.name}: Taak gestart, wacht {seconds} seconden.")
await asyncio.sleep(seconds)
print(f"{self.name}: Taak voltooid na {seconds} seconden.")
return f"Resultaat van {seconds} seconden"
async def main(self):
print(f"{self.name}: Start meerdere taken...")
# Voer meerdere taken gelijktijdig uit
results = await asyncio.gather(
self.async_task(1),
self.async_task(2),
self.async_task(3)
)
print(f"{self.name}: Alle taken voltooid. Resultaten: {results}")
def start(self):
# Start de event loop en voer de main coroutine uit
asyncio.run(self.main())
# Wanneer dit script direct wordt uitgevoerd, maakt het een instance van AsyncApp aan.
if __name__ == "__main__":
app = AsyncApp("AsyncAppInstance")
Asyncio gebruik in Streamlit app
π Asyncio in streamlit app loop d.m.v. nest-asyncio
π Asyncio in streamlit, OpenAI Agents SDK app
threading en asyncio
Threading en asyncio (soms met nest_asyncio) worden allebei gebruikt om meerdere dingen tegelijk te laten gebeuren. Maar ze werken op totaal verschillende manieren. Hieronder leg ik het verschil duidelijk uit, met voorbeelden en wanneer je wat moet gebruiken.
π§΅ threading: echte parallelliteit met meerdere OS-threads
Elke thread draait letterlijk tegelijk (losse werkeenheden).
- Geschikt voor wachten op externe dingen (I/O: netwerk, files), en voor eenvoudige parallelliteit zonder veel regels.
- Elke thread kan geblokkeerd worden zonder dat andere threads stoppen.
- Werkt goed met bestaande (blokkerende) bibliotheken zoals requests, input() enz.
β Gebruik threading als:
- Je niet met async def of await werkt;
- Je code blokkerende functies gebruikt (zoals time.sleep(), requests.get(), enz.);
- Je iets simpels wil doen zonder veel structuur of controlestromen.
βοΈ asyncio: cooperatieve, niet-blokkerende multitasking binnen één thread
- Werkt met een event loop waarin taken vrijwillig pauzeren met await.
- Niet βechtβ parallel β maar super efficiΓ«nt bij veel kleine I/O-wachttijden.
- Je gebruikt async def, await, en asyncio.create_task() om meerdere dingen tegelijk te laten lopen.
β Gebruik asyncio als:
- Je bibliotheken gebruikt die asynchroon zijn (zoals aiohttp, openai.AsyncOpenAI);
- Je applicatie veel tegelijk moet doen zonder blokkerende code;
- Je volledige controle wil over de volgorde van taken en pauzemomenten;
- Je een modernere (snellere) aanpak wil binnen één thread.
π¦ nest_asyncio: maakt asyncio herbruikbaar in een bestaande sync omgeving
- Normaal mag je maar één keer een event loop starten.
- In bijvoorbeeld Jupyter of streamlit is er al een loop actief.
- nest_asyncio laat je toch await gebruiken binnen die bestaande loop (meestal een hack).
β Gebruik nest_asyncio alleen als:
- Je in een omgeving zit waar al een event loop draait (zoals Jupyter, notebooks, Streamlit);
- Je asynchrone functies toch wilt kunnen gebruiken in een βgewoneβ omgeving.
π Praktisch verschil threading/asyncio
Stel je maakt een chatbot die een OpenAI-aanroep doet en tegelijk een loader laat zien:
Met threading
# gewone functie
def openai_call():
return requests.post("...") # blokkeert de thread
# loader in andere thread: threading.Thread
Met asyncio
# async functie
async def openai_call():
return await aiohttp.post("...") # niet-blokkerend
# loader tegelijk met asyncio.create_task(loader())
Bij asyncio moet alles β van de HTTP-client tot je eigen code β non-blocking zijn.
π€ Dus... wanneer gebruik je wat?
| Situatie | Gebruik |
| ββββββββββββββββ | ββββββββββ- |
| Werkt met `requests`, `input()`, `time.sleep()` | `threading` |
| Werkt met `aiohttp`, `async def`, `await` | `asyncio` |
| Je wil eenvoudig een loader starten | `threading` is vaak simpeler |
| Je bouwt een schaalbare server/app | `asyncio` (sneller en moderner) |
| Jupyter Notebook + asyncio | `asyncio` + `nest_asyncio` |
π Mixen?
Je kunt ze combineren, maar wees voorzichtig:
- Gebruik threading voor blokkerende functies in een asyncio-omgeving (via run_in_executor);
- Gebruik asyncio voor de rest, als je bibliotheken async ondersteunen.
