Speaker Andrew Godwin
Time 2019-08-02 14:10
Conference PyCon Au 2019
Talk details Link

Goal:

Write asyncio views just look normal views.

Overview

Threads are preemptive.

Coroutines are cooperative. If you don’t call await, this causes everything to wait.

Coroutines need an event loop.

Threads:

  • Threads are slow.
  • The more you add, the worse it gets.
  • Threads are restricted in number.

Async is fast

  • As long as you are I/O bound.

Async code is incompatable with non async code. Function cannot be both async and non-async at the same time.

It is possible to call non-async code from async code, but you need to be careful or you block async code. As a result, it is potentially dangerous. Need to run non-async code in seperate thread.

It is possible to call async from from non-async code. This is easier in Python 3.7

Should not write async code unless you need to. e.g. because code isn’t fast enough.

Django

  • Backwards compatibility is crucial.
  • Async functions need to be duplicated, and have different names.
  • One core Django, and two interfaces.

Not all language features work with asyncio.

  • Operator overloading.
  • Foreign keys.
  • One thread just for sync code.

Requirements:

  • Async has to add, not replace. Sync Django is important.
  • Things have to look familiar. Should still feel like Django.
  • Things need to be safe by default. Deadlocking/blocking is easy.

Django Implementation

We don’t want to have to versions of Django at any time.

Outside-in approach

Phase 1: ASGI/WSGI server. Django 3.0. Phase 2: Middleware/Handler/View. Django 3.1. Phase 3: ORM. Django 3.2/4.0.

Phase 1

Django WSGI abstraction layer was because Django predates WSGI, and looking for justification. ASGI is the justification.

ASGI is mostly ASGI compatable. bytes/string more clearly defined.

Async calling sync is dangerous. Implemented sync_to_async and async_to_sync.

  • Propagates exceptions nicely.
  • Proxies threadlocals correctly.
  • Stickies sync code into one thread. Nasty, but required to avoid rewriting Django.

Django 3.0 can speak ASGI. …but it can’t do much with it.

Phase 2

Need to update BaseHandler to support sync or async views.

There is a thirdhandler: TestClient. TestClient remains synchronous, this is not getting changes.

main thread -> event loop (async) -> new thread

Wrong! SQLite hates having multiple threads interact with database.

main thread -> event loop (async) -> main thread

Don’t block main thread. Awful code. There is a whole talk in how this works. Is at least functioning.

Middleware. Django 1.10 improved middleware, which made this harder.

Minimum change required. Backward compatibility required.

Transactions, templates, and traceback.

Traceback, long stack traces, makes it unmanageable.

Django 3.1 has async def views.

Phase 3

There is “for” and “async for”.

result = instance.foreign_key.name

We need to force use of select_related. However it is something you should be doing anyway.

Looking ahead

Cache? Templates? Forms?

Cache is good use case.

Some things don’t need async. Forms mostly CPU bound.

Performance is important. We don’t want to slow down Django. We don’t want to harm Django.