Asyncio met Python

2018-02-08

Sinds een aantal maanden gebruik ik de asyncio library van Python. Dit was in het begin een beetje puzzelen, maar eenmaal op gang is het prettig werken! Laat ik in deze blog heel kort ingaan op wat deze library is en waar hij handig voor is.

Asyncio, wat is dat?

Sinds Python 3.4 is asyncio toegevoegd aan de standaardlib. Eigenlijk is het een manier om heel efficiënt met één thread te werken. Hoewel het qua manier van werken wat weg heeft van multi-threading, is het dat niet.

Het idee is dat je taken aan een eventloop hangt. Deze taken noem je coroutines. Je herkent een coroutine aan het keywoord “async” vlak voor een functie. De eventloop houdt bij welke coroutine aan de beurt is. Deze coroutines moeten zelf hun beurt afgeven. Effectief is de werking hetzelfde als bij preemptive multitasking. Er is altijd maar één coroutine per loop tegelijk aan de beurt. Concurrency problematiek treedt hier dus niet op.

Asyncio is trouwens niet voorbehouden aan Python. Onder andere javascript en C# ondersteunen dit mechanisme ook.

En wat is er dan zo handig aan?

Zoals ik al zei, je kunt efficiënter met je threads omgaan. Als je bijvoorbeeld een REST call zou doen op een langzame api, dan zou een reguliere Python applicatie blokkeren of een extra thread nodig hebben voor een antwoord. Met asyncio geeft de coroutine gewoon de beurt af. Zodra er antwoord komt op de call, krijgt hij de beurt weer terug. Zo is er geen geblokkeerd programma of een slapende thread en is je programma meer lightweight.

Het grootste voordeel vind ik overigens hoe eenvoudig taken op elkaar kunnen wachten. Als je met libraries werkt die asynchrone functionaliteit bieden, dan krijg je al snel te maken met callbacks. Soms krijg je dan callback na callback… dit wordt ook wel de “callback hell” genoemd. Je code wordt er niet leesbaarder van. Met asyncio kun je leesbare code schrijven terwijl je asynchroon blijft.

Tijd voor een voorbeeldje. Stel dat je meerdere asynchrone functies achter elkaar moet aanroepen met telkens het resultaat van de vorige als argument. Dan zou dit er ongeveer zo uitzien in reguliere Python en met asyncio:

Callback hell

Asyncio

def func(): 
  def _callback_x(x):
    fetch_y(_callback_y, x)

  def _callback_y(y):
    fetch_z(_callback_z, y)

  def _callback_z(z):
    print(“Yuck!”)

  fetch_x(_callback_x)

 

async def func():
  x = await fetch_x()
  y = await fetch_y(x)
  z = await fetch_z(y)
  print(“Much cleaner code!”)

 

Toegegeven, je zou in de linker code ook lambda expressies kunnen gebruiken. Daar kom je helaas vaak niet mee weg, omdat Python alleen oneliners in lambda’s toestaat. Hoe dan ook, de rechter code hierboven is toch veel leesbaarder? :-)

Conclusie

Python’s asyncio is in het begin even doorbijten. Het vereist ook een iets andere manier van programmeren. Maar als je met asynchrone functionaliteit te maken krijgt, dan is dit zeker de moeite waard!


Bekijk alle posts van Ramon