Andrew Godwin veröffentlichte am 9. Mai DEP 0009: Async-fähiges Django und wurde am 21. Juli vom Django Technical Council genehmigt. Es ist also zu hoffen, dass sie bis zur Veröffentlichung von Django 3.0 etwas Interessantes tun können. Es wurde bereits irgendwo in den Kommentaren von Habr erwähnt , aber ich beschloss, diese Nachricht einem breiteren Publikum zu übermitteln, indem ich sie übersetzte - vor allem für diejenigen, die wie ich die Nachrichten von Django nicht besonders verfolgen.
Asynchrones Python wurde seit vielen Jahren entwickelt und im Django-Ökosystem haben wir in Channels damit experimentiert, wobei der Schwerpunkt auf der Unterstützung von Web-Sockets lag.
Als sich das Ökosystem entwickelte, stellte sich heraus, dass Django nicht dringend auf die Unterstützung von Nicht-HTTP-Protokollen wie Web-Sockets erweitert werden musste, die asynchrone Unterstützung jedoch viele Vorteile für das traditionelle Django-Framework für Modellansichtsvorlagen bieten würde.
Die Vorteile werden im Abschnitt Motivation unten beschrieben, aber die allgemeine Schlussfolgerung, zu der ich gekommen bin, ist, dass wir so viel von asynchronem Django bekommen, dass es die harte Arbeit wert ist, die es kostet. Ich glaube auch, dass es sehr wichtig ist, Änderungen auf iterative, von der Community unterstützte Weise vorzunehmen, die nicht von einem oder zwei alten Mitwirkenden abhängen, die möglicherweise ausbrennen.
Obwohl das Dokument als "Feature" -DEP bezeichnet wird, bedeutet dies, dass es teilweise auch eine Prozess-DEP ist. Der Umfang der unten vorgeschlagenen Änderungen ist unglaublich groß, und das Starten als traditioneller Einzelfunktionsprozess wird wahrscheinlich fehlschlagen.
Natürlich ist es in diesem Dokument wichtig, sich an die Django-Philosophie zu erinnern, die darin besteht, alles sicher und abwärtskompatibel zu halten. Der Plan ist nicht, synchrones Django zu entfernen - es ist geplant, es in seiner aktuellen Form beizubehalten, sondern Asynchronität als Option für diejenigen hinzuzufügen, die glauben, zusätzliche Leistung oder Flexibilität zu benötigen.
Ist das ein gigantischer Job? Natürlich. Ich bin jedoch der Meinung, dass dies die Zukunft von Django erheblich verändern kann - wir haben die Möglichkeit, einen bewährten Rahmen und eine unglaubliche Community zu nutzen und eine völlig neue Reihe von Optionen einzuführen, die zuvor unmöglich waren.
Das Web hat sich geändert, und Django sollte sich damit ändern, aber gemäß unseren Idealen erschwinglich, standardmäßig sicher und flexibel sein, wenn Projekte wachsen und sich ihre Anforderungen ändern. In der Welt des Cloud Data Warehousing, der serviceorientierten Architektur und des Backends als Grundlage komplexer Geschäftslogik ist die Fähigkeit, wettbewerbsfähig zu arbeiten, von entscheidender Bedeutung.
Diese DEP skizziert einen Plan, von dem ich denke, dass er uns dorthin führen wird. Dies ist eine Vision, an die ich wirklich glaube und mit der ich daran arbeiten werde, alles Mögliche zu tun. Gleichzeitig sind sorgfältige Analyse und Skepsis gerechtfertigt; Ich bitte um Ihre konstruktive Kritik sowie um Ihr Vertrauen. Django ist auf eine Community von Menschen und die von ihnen erstellten Anwendungen angewiesen. Wenn wir den Weg in die Zukunft bestimmen müssen, müssen wir dies gemeinsam tun.
Kurzbeschreibung
Wir werden Django um Unterstützung für asynchrone Darstellungen, Middleware, ORM und andere wichtige Elemente erweitern.
Dazu wird synchroner Code in Threads ausgeführt und schrittweise durch asynchronen Code ersetzt. Synchrone APIs bleiben bestehen und werden vollständig unterstützt. Im Laufe der Zeit werden sie zu synchronen Wrappern für anfänglich asynchronen Code.
Im ASGI-Modus wird Django als native asynchrone Anwendung gestartet. Der WSGI-Modus löst bei jedem Zugriff auf Django eine separate Ereignisschleife aus, sodass die asynchrone Schicht mit dem synchronen Server kompatibel ist.
Multithreading um ORMs ist komplex und erfordert ein neues Konzept von Verbindungskontexten und Sticky Threads, um synchronen ORM-Code auszuführen.
Viele Teile von Django werden weiterhin synchron arbeiten. Unsere Priorität wird darin bestehen, Benutzer beim Schreiben von Ansichten in beiden Stilen zu unterstützen, damit sie den besten Stil für die Präsentation auswählen können, an der sie arbeiten.
Einige Funktionen, wie Vorlagen und Caching, benötigen separate DEPs und Studien, wie sie vollständig asynchron gemacht werden können. Diese DEP konzentriert sich hauptsächlich auf den HTTP-Middleware-View-Flow und ORM.
Es wird volle Abwärtskompatibilität geben. Ein Standard-Django 2.2-Projekt sollte unverändert in asynchronem Django (sei es 3.0 oder 3.1) ausgeführt werden.
Dieser Vorschlag konzentriert sich auf die Implementierung kleiner, iterativer Teile mit ihrer schrittweisen Platzierung in der Hauptniederlassung, um Probleme mit der langlebigen Gabel zu vermeiden und es uns zu ermöglichen, den Kurs zu ändern, wenn wir Probleme erkennen.
Dies ist eine gute Gelegenheit, neue Mitglieder zu gewinnen. Wir müssen das Projekt finanzieren, damit dies schneller geschieht. Die Finanzierung sollte in einem Umfang erfolgen, an den wir nicht gewöhnt sind.
Spezifikation
Das übergeordnete Ziel besteht darin, jeden einzelnen Teil von Django, der blockiert werden kann, dh nicht nur CPU-gebundene Berechnungen, asynchron zu machen (in einer asynchronen Ereignisschleife ohne Sperren auszuführen).
Dies umfasst die folgenden Funktionen:
- Zwischenschichten (Middleware)
- Ansichten
- ORM
- Muster
- Testen
- Caching
- Formularvalidierung
- E-Mail
Dies schließt jedoch Dinge wie Internationalisierung nicht ein, die keinen Leistungsgewinn bringen, da dies eine CPU-gebundene Aufgabe ist, die ebenfalls schnell ausgeführt wird, oder Migrationen, die beim Starten über den Verwaltungsbefehl Single-Threaded sind.
Jede einzelne Funktion, die im Inneren asynchron wird, bietet auf absehbare Zeit auch eine synchrone Schnittstelle, die abwärtskompatibel mit der aktuellen API (in 2.2) ist. Wir könnten sie im Laufe der Zeit ändern, um sie zu verbessern, aber synchrone APIs werden nirgendwo hingehen.
Im Folgenden wird ein Überblick darüber gegeben, wie dies technisch erreicht wird, und anschließend werden spezifische Implementierungsdetails für bestimmte Bereiche angegeben. Es ist nicht für alle Funktionen von Django erschöpfend, aber wenn wir dieses ursprüngliche Ziel erreichen, werden wir fast alle Anwendungsfälle einbeziehen.
Im letzten Teil dieses Abschnitts, „Vorgehensweise“, wird auch erläutert, wie diese Änderungen schrittweise und von mehreren Entwicklergruppen parallel implementiert werden können. Dies ist wichtig, um diese Änderungen mithilfe von Freiwilligen in angemessener Zeit durchzuführen.
Technische Überprüfung
Das Prinzip, das es uns ermöglicht, synchrone und asynchrone Implementierungen parallel zu verwalten, ist die Fähigkeit, einen Stil in einem anderen auszuführen.
Jede Funktion durchläuft drei Implementierungsstufen:
- Nur synchron (wir sind hier)
- Synchrone Implementierung mit asynchronem Wrapper
- Asynchrone Implementierung mit synchronem Wrapper
Asynchroner Wrapper
Zunächst wird der vorhandene synchrone Code in eine asynchrone Schnittstelle eingeschlossen, die den synchronen Code im Thread-Pool ausführt. Auf diese Weise können wir relativ schnell eine asynchrone Schnittstelle entwerfen und bereitstellen, ohne den gesamten verfügbaren Code für die Asynchronität neu schreiben zu müssen.
Das Toolkit hierfür ist bereits in asgiref als Funktion sync_to_async
, die Dinge wie Ausnahmebehandlung oder Threadlocals unterstützt (mehr dazu weiter unten).
Das Ausführen von Code in Threads führt höchstwahrscheinlich nicht zu einer Steigerung der Produktivität - der auftretende Overhead verlangsamt ihn wahrscheinlich ein wenig, wenn Sie nur normalen linearen Code ausführen -, aber dies ermöglicht Entwicklern, wettbewerbsfähig etwas auszuführen und sich an neue Funktionen zu gewöhnen.
Darüber hinaus gibt es mehrere Teile von Django, die bei wiederholtem Zugriff empfindlich darauf reagieren, im selben Thread zu beginnen. Zum Beispiel das Verarbeiten von Transaktionen in der Datenbank. Wenn wir einen Code in atomic()
einschließen würden, der dann über zufällige Threads aus dem Pool auf das ORM zugreifen würde, hätte die Transaktion keine Auswirkung, da sie an die Verbindung innerhalb des Threads gebunden ist, in dem die Transaktion gestartet wurde.
In solchen Situationen ist ein „klebriger Thread“ erforderlich, in dem der asynchrone Kontext den gesamten synchronen Code im selben Thread nacheinander aufruft, anstatt ihn in den Thread-Pool zu verschieben, während das korrekte Verhalten von ORM und anderen threadempfindlichen Teilen beibehalten wird. Alle Teile von Django, von denen wir vermuten, dass sie benötigt werden, einschließlich des gesamten ORM, verwenden die Version sync_to_async
, die dies berücksichtigt, sodass standardmäßig alles sicher ist. Benutzer können dies für die Ausführung wettbewerbsfähiger Abfragen selektiv deaktivieren. Weitere Informationen finden Sie unter "ORM" weiter unten.
Asynchrone Implementierung
Der nächste Schritt besteht darin, die Implementierung der Funktion in asynchronen Code umzuschreiben und dann die synchrone Schnittstelle über einen Wrapper zu präsentieren, der asynchronen Code in einer einmaligen Ereignisschleife ausführt. Dies ist in asgiref bereits als async_to_sync
Funktion verfügbar.
Es ist nicht erforderlich, alle Funktionen gleichzeitig neu zu schreiben, um schnell zur dritten Stufe zu gelangen. Wir können unsere Bemühungen auf die Teile konzentrieren, die wir gut können und die von Bibliotheken von Drittanbietern unterstützt werden, während wir dem Rest des Python-Ökosystems bei Dingen helfen, die mehr Arbeit erfordern, um native Asynchronität zu implementieren. Dies wird unten diskutiert.
Diese allgemeine Übersicht funktioniert mit fast allen Django-Funktionen, die asynchron werden sollen, mit Ausnahme der Stellen, für die Python keine bereits verwendeten asynchronen Funktionsäquivalente bereitstellt. Das Ergebnis wird entweder eine Änderung in der Darstellung der API von Django im asynchronen Modus sein oder die Zusammenarbeit mit Python-Kernentwicklern, um die Entwicklung asynchroner Python-Funktionen zu unterstützen.
Threadlocals
Eines der grundlegenden Details der Django-Implementierung, die getrennt von den meisten der unten beschriebenen Funktionen erwähnt werden müssen, sind Threadlocals. Wie der Name schon sagt, funktionieren Threadlocals innerhalb eines Threads, und obwohl Django das HttpRequest
Objekt außerhalb von threadlocal hält, fügen wir einige andere Dinge hinzu - zum Beispiel Datenbankverbindungen oder die aktuelle Sprache.
Die Verwendung von Threadlocals kann in zwei Optionen unterteilt werden:
- "Kontext-Lokale", bei denen ein Wert in einem stapelbasierten Kontext benötigt wird, z. B. eine Anforderung. Dies ist erforderlich, um die aktuelle Sprache einzustellen.
- "True Threadlocals", bei denen der geschützte Code für den Aufruf von einem anderen Thread aus nicht sicher ist. Dies dient zum Herstellen einer Verbindung mit der Datenbank.
Auf den ersten Blick scheint es, dass "Kontextlokale" mit dem neuen Kontextvars- Modul in Python aufgelöst werden können, aber Django 3.0 muss weiterhin Python 3.6 unterstützen, während dieses Modul in 3.7 erschien. Darüber hinaus wurde contextvars
speziell entwickelt, um den Kontext zu contextvars
wenn beispielsweise zu einem neuen Thread sync_to_async
Diese Werte müssen jedoch sync_to_async
werden, damit die Funktionen sync_to_async
und async_to_sync
normal als Wrapper fungieren können. Wenn Django nur 3.7 und höher unterstützt, könnten wir die Verwendung von contextvars
Betracht contextvars
, aber dies würde erhebliche Arbeit in Django erfordern.
Dies wurde bereits mit asgiref Local
behoben , das mit Coroutinen und Threads kompatibel ist. Jetzt werden keine contextvars
, aber wir können es nach einigen Tests auf Backport für 3.6 umstellen.
Echte Threadlocals hingegen können im aktuellen Thread einfach weiterarbeiten. Wir müssen jedoch vorsichtiger sein, um zu verhindern, dass solche Objekte in einen anderen Strom gelangen. Wenn die Präsentation nicht mehr im selben Thread ausgeführt wird, sondern für jeden ORM-Aufruf einen Thread erzeugt (während der Phase „Synchrone Implementierung, asynchroner Wrapper“), sind einige Dinge, die im synchronen Modus möglich waren, im asynchronen Modus nicht möglich.
Dies erfordert besondere Aufmerksamkeit und das Verbot einiger zuvor möglicher Operationen im asynchronen Modus. Die uns bekannten Fälle werden im Folgenden in bestimmten Abschnitten beschrieben.
Gleichzeitige Unterstützung für synchrone und asynchrone Schnittstellen
Eines der großen Probleme beim Portieren von Django ist, dass Sie mit Python keine synchronen und asynchronen Versionen einer Funktion mit demselben Namen erstellen können.
Dies bedeutet, dass Sie nicht einfach eine API erstellen können, die wie folgt funktioniert:
Dies ist eine unglückliche Einschränkung der asynchronen Implementierung von Python, und es gibt keine offensichtliche Problemumgehung. Wenn etwas angerufen wird, wissen Sie nicht, ob Sie erwartet werden oder nicht. Daher können Sie nicht feststellen, was zurückgegeben werden muss.
(Hinweis: Dies liegt daran, dass Python asynchrone Funktionen als "synchrones aufrufbares __acall__
, das eine Coroutine zurückgibt" und nicht als "Aufrufen der __acall__
Methode für ein Objekt" __acall__
. Asynchrone Kontextmanager und Iteratoren haben dieses Problem nicht, da Sie haben getrennte Methoden __aiter__
und __aenter__
.)
In diesem Sinne müssen wir die Namespaces von synchronen und asynchronen Implementierungen getrennt voneinander platzieren, damit sie nicht in Konflikt geraten. Wir könnten dies mit dem genannten Argument sync=True
tun, aber dies führt zu verwirrenden Funktionskörpern / Methoden und verhindert die Verwendung von async def
und ermöglicht es Ihnen auch, versehentlich zu vergessen, dieses Argument zu schreiben. Ein zufälliger Aufruf einer synchronen Methode, wenn Sie sie asynchron aufrufen möchten, ist gefährlich.
Die vorgeschlagene Lösung für die meisten Stellen in der Django-Codebasis besteht darin, ein Suffix für Namen von asynchronen Implementierungen von Funktionen cache.get_async
- beispielsweise cache.get_async
zusätzlich zu synchronem cache.get
. Obwohl dies eine hässliche Lösung ist, ist es sehr einfach, Fehler beim Anzeigen von Code zu erkennen (Sie sollten _async
mit der Methode _async
verwenden).
Ansichten und HTTP-Behandlung
Ansichten sind wahrscheinlich der Grundstein für die Nützlichkeit der Asynchronität, und wir erwarten, dass die meisten Benutzer zwischen asynchronem und synchronem Code wählen.
Django wird zwei Arten von Ansichten unterstützen:
- Synchrone Darstellungen, definiert wie bisher durch eine synchrone Funktion oder Klasse mit synchronem
__call__
- Asynchrone Darstellungen, die durch eine asynchrone Funktion (Rückgabe einer Coroutine) oder eine Klasse mit asynchronem
__call__
.
Sie werden von BaseHandler
, der die vom URL-Resolver empfangene Ansicht überprüft und entsprechend BaseHandler
. Der Basishandler sollte der erste Teil von Django sein, der asynchron wird, und wir müssen den WSGI-Handler so ändern, dass er mit async_to_sync
in einer eigenen Ereignisschleife async_to_sync
.
Zwischenebenen (Middleware) oder Einstellungen wie ATOMIC_REQUESTS
, die die Ansichten in nicht asynchronen sicheren Code ATOMIC_REQUESTS
(z. B. den atomic()
Block), funktionieren weiterhin, ihre Geschwindigkeit wird jedoch beeinträchtigt (z. B. das Verbot paralleler ORM-Aufrufe in der Ansicht mit atomic()
)
Die vorhandene StreamingHttpResponse
Klasse wird so geändert, dass sie entweder einen synchronen oder einen asynchronen Iterator akzeptiert. Die interne Implementierung erfolgt dann immer asynchron. Ähnliches gilt für FileResponse
. Da dies ein potenzieller __iter__
für Code von Drittanbietern ist, der direkt auf __iter__
zugreift, müssen wir für die Übergangszeit weiterhin einen synchronen __iter__
bereitstellen.
WSGI wird weiterhin auf unbestimmte Zeit von Django unterstützt, aber der WSGI-Handler wird weiterhin asynchrone Middleware und Ansichten in einer eigenen einmaligen Ereignisschleife ausführen. Dies führt wahrscheinlich zu einem leichten Leistungsabfall, hatte jedoch in den ersten Experimenten keine allzu großen Auswirkungen.
Alle asynchronen HTTP-Funktionen funktionieren in WSGI, einschließlich langer Abfragen und langsamer Antworten. Sie sind jedoch genauso ineffizient wie jetzt und belegen für jede Verbindung einen Thread / Prozess. ASGI-Server sind die einzigen, die viele gleichzeitige Anforderungen effizient unterstützen und Nicht-HTTP-Protokolle wie WebSocket für die Verwendung durch Erweiterungen wie Channels verarbeiten können .
Zwischenschichten
Während sich der vorherige Abschnitt hauptsächlich auf den Anforderungs- / Antwortpfad konzentrierte, benötigt Middleware aufgrund der Komplexität ihres aktuellen Designs einen separaten Abschnitt.
Django-Middlewares sind jetzt in Form eines Stapels angeordnet, in dem jede Middleware get_response
erhält, um die nächste Middleware in der angegebenen Reihenfolge auszuführen (oder die Ansicht für die niedrigste Middleware auf dem Stack). Aus Gründen der Abwärtskompatibilität müssen wir jedoch eine Mischung aus synchroner und asynchroner Middleware beibehalten, und diese beiden Typen können nicht nativ aufeinander zugreifen.
Um sicherzustellen, dass die Middleware funktioniert, müssen wir stattdessen jede Middleware mit dem Platzhalter get_response initialisieren, der stattdessen die Kontrolle an den Handler zurückgibt und sowohl die Datenübertragung zwischen der Middleware und der Ansicht als auch einen Ausnahmefall übernimmt. In gewisser Weise wird es aus interner Sicht irgendwann wie Middleware der Django 1.0-Ära aussehen, obwohl die Benutzer-API natürlich dieselbe bleibt.
Wir können synchrone Middleware für veraltet erklären, aber ich empfehle, dies nicht bald zu tun. Wenn wir am Ende des Zyklus ihrer Veralterung angelangt sind, könnten wir die Middleware-Implementierung auf ein rein rekursives Stack-Modell zurückführen, wie es jetzt ist.
ORM
ORM ist der größte Teil von Django in Bezug auf die Codegröße und am schwierigsten in asynchron zu konvertieren.
Dies ist hauptsächlich auf die Tatsache zurückzuführen, dass die zugrunde liegenden Datenbanktreiber vom Design her synchron sind und nur langsam Fortschritte bei einer Reihe ausgereifter, standardisierter, asynchroner Datenbanktreiber erzielt werden. Stattdessen müssen wir eine Zukunft entwerfen, in der Datenbanktreiber zunächst synchron sein werden, und den Grundstein für Mitwirkende legen, die asynchrone Treiber iterativ weiterentwickeln.
Probleme mit ORM lassen sich in zwei Hauptkategorien einteilen: Threads und implizites Blockieren.
Streams
Das Hauptproblem bei ORM besteht darin, dass Django um ein einzelnes globales connections
herum entworfen wurde, wodurch Sie auf magische Weise die richtige Verbindung für Ihren aktuellen Thread erhalten.
In einer asynchronen Welt, in der alle Coroutinen im selben Thread arbeiten, ist dies nicht nur ärgerlich, sondern einfach gefährlich. Ohne zusätzliche Sicherheit kann ein Benutzer, der wie gewohnt auf ein ORM zugreift, Verbindungsobjekte beschädigen, indem er von mehreren verschiedenen Stellen aus darauf zugreift.
Glücklicherweise sind Verbindungsobjekte zumindest zwischen Threads portierbar, obwohl sie nicht von zwei Threads gleichzeitig aufgerufen werden können. Django kümmert sich bereits im ORM-Code um die Thread-Sicherheit für Datenbanktreiber. Daher können wir das Verhalten ändern, damit es ordnungsgemäß funktioniert.
Wir werden das connections
so ändern, dass es sowohl Coroutinen als auch Threads versteht. Dabei wird Code aus asgiref.local
, jedoch mit zusätzlicher Logik. Verbindungen werden in asynchronem und synchronem Code geteilt, der sich gegenseitig aufruft - wobei der Kontext sync_to_async
und async_to_sync
- und der synchrone Code wird gezwungen, nacheinander in einem Sticky-Thread ausgeführt zu werden, sodass dies nicht funktioniert gleichzeitig Fadensicherheit brechen.
Dies impliziert, dass wir eine Lösung wie einen Kontextmanager benötigen, um eine Datenbankverbindung wie atomic()
zu öffnen und zu schließen. Auf diese Weise können wir in diesem Kontext einen konsistenten Aufruf und Sticky-Threads bereitstellen und Benutzer können mehrere Kontexte erstellen, wenn sie mehrere Verbindungen öffnen möchten. Es gibt uns auch eine Möglichkeit, magische globale connections
loszuwerden, wenn wir dies weiterentwickeln wollen.
Derzeit verfügt Django nicht über ein Verbindungslebenszyklusmanagement, das unabhängig von den Signalen der Handlerklasse ist. Daher werden wir sie verwenden, um diese „Verbindungskontexte“ zu erstellen und zu löschen. Die Dokumentation wird ebenfalls aktualisiert, um zu verdeutlichen, wie Verbindungen außerhalb des Anforderungs- / Antwortzyklus ordnungsgemäß behandelt werden. Selbst im aktuellen Code wissen viele Benutzer nicht, dass ein langjähriges Management-Team regelmäßig close_old_connections
aufrufen muss, um close_old_connections
zu funktionieren.
Abwärtskompatibilität bedeutet, dass wir Benutzern jederzeit den Zugriff auf connections
von jedem beliebigen Code aus erlauben müssen, dies jedoch nur für synchronen Code zulassen. Wir werden sicherstellen, dass der Code vom ersten Tag an in einen „Verbindungskontext“ eingeschlossen wird, wenn er asynchron ist.
Es scheint, als wäre es schön, zusätzlich zu transaction.atomic()
Datei transaction.atomic()
hinzuzufügen und den Benutzer zu verpflichten, den gesamten Code in einem von ihnen auszuführen. Dies kann jedoch zu Verwirrung darüber führen, was beim Anhängen passiert einer von ihnen ist im anderen.
Stattdessen schlage ich vor, einen neuen Kontextmanager db.new_connections()
erstellen, der dieses Verhalten db.new_connections()
, und bei jedem Aufruf eine neue Verbindung zu erstellen und eine beliebige Verschachtelung von atomic()
zuzulassen.
Jedes Mal, wenn Sie new_connections()
Block new_connections()
, richtet Django einen neuen Kontext mit neuen Datenbankverbindungen ein. Alle Transaktionen, die außerhalb des Blocks ausgeführt wurden, werden fortgesetzt. Alle ORM-Aufrufe innerhalb des Blocks arbeiten mit einer neuen Verbindung zur Datenbank und sehen die Datenbank unter diesem Gesichtspunkt. Wenn die Transaktionsisolation in der Datenbank aktiviert ist, wie dies normalerweise standardmäßig der Fall ist, bedeutet dies, dass bei neuen Verbindungen innerhalb des Blocks möglicherweise nicht die Änderungen angezeigt werden, die von nicht festgeschriebenen Transaktionen außerhalb des Blocks vorgenommen wurden.
Darüber hinaus können die Verbindungen in diesem Block new_connections
selbst atomic()
, um zusätzliche Transaktionen für diese neuen Verbindungen auszulösen. Jede Verschachtelung dieser beiden Kontextmanager ist zulässig, aber jedes Mal, new_connections
verwendet wird, werden zuvor geöffnete Transaktionen "angehalten" und wirken sich nicht auf ORM-Aufrufe aus, bis ein neuer new_connections
Block new_connections
.
Ein Beispiel dafür, wie diese API aussehen könnte:
async def get_authors(pattern):
Dies ist etwas ausführlich, aber das Ziel besteht auch darin, Verknüpfungen auf hoher Ebene hinzuzufügen, um dieses Verhalten zu ermöglichen (und auch den Übergang von asyncio.ensure_future
in Python 3.6 zu asyncio.create_task
in 3.7 asyncio.create_task
).
Mit diesem Kontextmanager und Sticky Streams im selben Verbindungskontext garantieren wir, dass der gesamte Code standardmäßig so sicher wie möglich ist. Es besteht die Möglichkeit, dass der Benutzer die Verbindung in einem Thread für zwei verschiedene Teile der Anforderung mithilfe von yield
kann. Dies ist jedoch yield
jetzt möglich.
Implizite Sperren
Ein weiteres Problem des aktuellen ORM-Entwurfs besteht darin, dass in Modellinstanzen blockierende (netzwerkbezogene) Operationen, insbesondere lesebezogene Felder, auftreten.
Wenn Sie eine Modellinstanz nehmen und dann auf model_instance.related_field
zugreifen, model_instance.related_field
Django den Inhalt des zugeordneten Modells transparent und gibt ihn an Sie zurück. Dies ist jedoch im asynchronen Code nicht möglich. Blockierungscode sollte nicht im Hauptthread ausgeführt werden, und es gibt keinen asynchronen Zugriff auf Attribute.
Glücklicherweise hat Django bereits einen Ausweg: select_related
, das die zugehörigen Felder im Voraus lädt, und prefetch_related
für viele-zu-viele-Beziehungen. Wenn Sie ORM asynchron verwenden, verbieten wir implizit blockierende Vorgänge, z. B. den Hintergrundzugriff auf Attribute, und geben stattdessen einen Fehler zurück, der angibt, dass Sie das Feld zuerst abrufen müssen.
Dies hat den zusätzlichen Vorteil, dass langsamer Code verhindert wird, der N Anforderungen in einer for
Schleife ausführt, was ein häufiger Fehler vieler neuer Django-Programmierer ist. Dies erhöht die Eintrittsbarriere, aber denken Sie daran, dass asynchrones Django optional ist - Benutzer können weiterhin synchronen Code schreiben, wenn sie dies wünschen (und dies wird im Tutorial empfohlen, da es viel schwieriger ist, synchronen Code zu machen).
Glücklicherweise kann QuerySet
problemlos asynchrone Generatoren implementieren und sowohl Synchronisation als auch Asynchronität transparent unterstützen:
async def view(request): data = [] async for user in User.objects.all(): data.append(await extract_important_info(user)) return await render("template.html", data)
Andere
Teile von ORM, die mit Schemaänderungen verknüpft sind, sind nicht asynchron. Sie sollten nur von Managementteams aufgerufen werden. Einige Projekte nennen sie bereits in Einsendungen, aber das ist sowieso keine gute Idee.
Muster
Die Vorlagen sind jetzt vollständig synchron, und es ist geplant, sie im ersten Schritt so zu belassen. , , DEP.
, Jinja2 , .
, Django , . Jinja2 , , , .
, render_async
, render
; , , .
Django — _async
- (, get_async
, set_async
).
, API sync_to_async
, BaseCache
.
, thread-safety API , Django, , . , ORM, , .
, , , ModelForm
ORM .
, - clean
save
, , . , , , DEP.
E-Mail
Django, . send_mail_async
send_mail
, async
- (, mail_admins
).
Django , - SMTP, . , , , , .
Testen
, Django .
ASGI- asgiref.testing.ApplicationCommunicator
. assert' .
Django , , . , — , , HTTP event loop, WSGI.
. , , .
, , . async def
@async_to_sync
, , , Django test runner.
asyncio ( loop' , ) , , , DEBUG=True
. — , , .
WebSockets
Django; , Channels , ASGI, .
, Channels, , ASGI.
, , . , .
, . , — .
, , . , ORM, , , .
:
; , . , , , , .
, - ; , , . , , Django, Django async-only .
, , DEP , , , email . DBAPI — , core Python , , PEP, .
, , , . Django , - , -; .
, - . , — , , .
Python — . - Python , , .
Python asyncio
, , . , , , , Django-size .
Django, «»; , , — , Django — .
, . , API , Django - .
, , Django . , Django ORM , , , -.
, , — . - long-poll server-sent events. Django , - .
Django; . , , , .
Django , . ; Django- , , , , , .
, -- Django .
, . « Django», ; , , .
, , , , API Django, , , Python 3, API, Django Python.
Python
Python . Python, , , .
, Django — - Python — , Python, Python . , , , .
, Django, . , Django , .
— , , , , .
, — , , — Django ( , Python ).
Django?
, Django. , Lawrence Journal-World — , , SPA — , , . , , , , .
, Django , - , — — . , , ; , Django , .
, Django . , . , , , — , , .
Django django-developers , , , , DEP.
, , , :
- : master- Django .
- : Django , , , , Django .
- : , , Django, , Django. , , , , .
Channels DEP ; Django , .
, , . DEP , , — Django .
. Django, , , WSGI , event loop , . 10% — , . , .
, , (- , Python ). , Django ; , , , Django master- .
, ( ORM, ..), ; Python.
, , Django, «» . , , , , .
Alternativen
, , , .
_async
, , (, django.core.cache.cache.get_async
), :
from django.core.cache_async import cache cache.get("foo")
, ; , , .
, , ; .
Django
- , ; , , , — .
, , , — , .
Channels
, Channels , «» Django. , - , , Django; ORM, HTTP/middleware flow .
asyncio
event loop' Python, asyncio
, Django. await
async
Python event loop .
, asyncio
, ; Django , , . Django ; , , async runtime, , .
Greenlets/Gevent
Gevent, , Python.
, . yield
await
, API, Django, , . , .
, , , . greenlet-safe Django ORM - new-connection-context, .
, . Django « » gevent, , , , .
DEP.
, — , — , ( ).
, , - . Django Fellows ; — , ( ), , , - .
— , Kickstarter migrations
contrib.postgres
, MOSS (Mozilla) Channels . , Django, , .
, . — Python, Django — — . , Django/async, .
HTTP/middleware/view flow, , , , « », .
, , , ( , Fellows, / , Channels, , ), , .
, , , , Django, .
, , , , API.
, , , , HTTP/middleware flow. , API, APM, .
, , Django , , . , ORM , , — , ORM .
DEP , ; Django .
, asgiref , , . Django Django.
Channels , Django, Django.
( ) CC0 1.0 Universal .