{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Async and Await Primer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A quick primer on `async`/`await`. Async and await are relatively new features in Python which allow **concurent** programming. They won't make your code magically faster, but may make your code easier to read, maintain and reason about. \n", "You will likely hear the terms event-loop, coroutines and many other ones, they will make sens in time. \n", "\n", "The key thing to remember is that \n", " - async-functions can call both sync and async functions.\n", " - sync functions can only call sync. \n", " - You _must_ always `await `\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Event loop\n", "\n", "It's like the \"One ring\", there shoudl be only one. IPython (and Jupyter) usually already run one.\n", "\n", "### Bad news\n", "If you need to run any code that need to create and manage an event-loop, consult the docs. \n", "Typically you can't run a tornado app inside jupyter.\n", "\n", "### Good news\n", "\n", "If you don't know/don't care, all is already setup for you. \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example\n", "\n", "Let's deactivate enventloop integration and try what is (usually invalid Python)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%autoawait False" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from asyncio import sleep" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# does not sleep, need to be awaited\n", "print('before sleep')\n", "sleep(5)\n", "print('after sleep')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await sleep(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f():\n", " await sleep(5)\n", "f()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async def f():\n", " print('before...')\n", " await sleep(5)\n", " print('after')\n", "### does not call f\n", "f()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "await f()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... back to step beginning. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Autoawait" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Autoawait will _attempt_ to detect async code and run it for you. There are of course limitations (bug report welcome)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%autoawait True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You will note that any line that start with `%` is invalid Python and are IPython specific syntax. Those are call magics (line-magics with a single `%` sign, cell magics with a double `%%` sign)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('before')\n", "await sleep(5)\n", "print('after')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top level await is now valid syntax. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tpl = 'https://anapioficeandfire.com/api/characters/{}'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%time\n", "\n", "results = []\n", "for i in range(1,50):\n", " import requests\n", " print('.', end='')\n", " r = requests.get(tpl.format(i)).json()['aliases']\n", " print('x', end='')\n", " results.append(r)\n", " \n", "for r in results:\n", " print(r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Moving to asynchronous" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nothing is perfect; if you get RuntimeErrors with asyncio, you may need to restart your kernel. More during my colleagues aiohttp tutorial this Afternoon" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import aiohttp" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async with aiohttp.ClientSession() as session:\n", " response = await session.get(tpl.format(583))\n", " json = await response.json()\n", " print(json['aliases'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async def get_char(i, session):\n", " print('.', end='')\n", " response = await session.get(tpl.format(i))\n", " json = await response.json()\n", " print('x', end='')\n", " return json['aliases']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async with aiohttp.ClientSession() as s:\n", " print(await get_char(1303, s))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tasks = []" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "async with aiohttp.ClientSession() as session:\n", " # start \n", " for i in range(1,50):\n", " task = asyncio.ensure_future(get_char(i, session))\n", " tasks.append(task)\n", " results = await asyncio.gather(*tasks)\n", " for r in results:\n", " print(r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Advance Autoawait usage, Exercise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Find the documentation for autoawait, and try to make it work with another asynchronous library. For exampe try to ply with [`trio`](https://trio.readthedocs.io/en/latest/), using `trio.sleep` and `trio.open_nursery` to get several concurent task running, pritning different message regularly and at random intervals. What happen if you use `time.sleep()` instead of `trio.sleep()` ? What hapen if you use `asyncio.sleep()` ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import trio" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async def every(n, message):\n", " for i in range(30):\n", " await trio.sleep(n)\n", " print(message)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%autoawait trio" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async with trio.open_nursery() as nursery:\n", " nursery.start_soon(every, 1, 'Plic')\n", " nursery.start_soon(every, 2, 'Ploc')\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }