Mein Ziel ist es, einen asynchronen, selbst gehosteten WCF-Dienst zu implementieren, der alle Anforderungen in einem einzigen Thread ausführt und die neuen C # 5-Async-Features voll ausnutzt.
Mein Server wird eine Konsolen-App sein, in der ich ein SingleThreadSynchronizationContext
einrichten werde, wie angegeben hier , erstellen und öffnen Sie ein ServiceHost Führen Sie anschließend SynchronizationContext
aus, sodass alle WCF-Anforderungen im selben Thread behandelt werden.
Obwohl der Server alle Anforderungen im selben Thread erfolgreich verarbeiten konnte, blockieren async-Vorgänge die Ausführung und werden serialisiert, anstatt interlaced zu werden.
Ich habe eine vereinfachte Probe vorbereitet, die das Problem reproduziert.
Hier ist mein Servicevertrag (der gleiche für Server und Client):
%Vor%Die Implementierung des Dienstes ist die folgende (sie ist ein wenig vereinfacht, aber die endgültige Implementierung kann auf Datenbanken zugreifen oder sogar andere Dienste asynchron aufrufen):
%Vor%Der Dienst wird in einer Konsolenanwendung gehostet:
%Vor% Wie Sie sehen können, ist das erste, was ich tue, einen einzelnen Thread SynchronizationContext
einzurichten. Anschließend erstelle, konfiguriere und öffne ich einen ServiceHost. Laut diesem Artikel , wie ich den SynchronizationContext vor seiner Erstellung festgelegt habe, die ServiceHost
erfasst es und alle Client-Anfragen werden in SynchronizationContext
veröffentlicht. In der Sequenz starte ich SingleThreadSynchronizationContext
im selben Thread.
Ich habe einen Test-Client erstellt, der den Server in einer "Fire-and-Forget" -Methode aufruft.
%Vor%Wenn ich das Beispiel ausführe, bekomme ich folgende Ergebnisse:
Kunde
Server
Die Nachrichten werden vom Client mit einem minimalen Intervall (& lt; 1s) gesendet.
Ich erwartete, dass der Server den ersten Aufruf erhalten würde, und führte ihn in SingleThreadSynchronizationContext
aus (ein neues WorkItem
einreihen. Wenn das await
Schlüsselwort erreicht war, wurde SynchronizationContext
noch einmal erfasst, die Fortsetzung wurde gepostet), und die Methode würde an dieser Stelle eine Aufgabe zurückgeben, die SynchronizationContext
freigibt, um mit der zweiten Anfrage fertig zu werden (zumindest damit anzufangen).
Wie Sie anhand der ID des Threads im Serverprotokoll sehen können, werden die Anforderungen korrekt in SynchronizationContext
gepostet. Betrachtet man jedoch die Zeitstempel, können wir sehen, dass die erste Anfrage abgeschlossen wird, bevor die zweite gestartet wird, was den Zweck eines asynchronen Servers völlig zunichte macht.
Warum passiert das?
Welcher Art ist die Implementierung eines selbst gehosteten asynchronen WCF-Servers?
Ich denke, das Problem ist mit dem SingleThreadSynchronizationContext, aber ich kann nicht sehen, wie man es auf andere Weise implementiert.
Ich habe das Thema recherchiert, aber ich konnte keine nützlicheren Informationen zum asynchronen WCF-Service-Hosting finden, insbesondere nicht mit dem aufgabenbasierten Muster.
ZUSATZ
Hier ist meine Implementierung von SingleThreadedSinchronizationContext
. Es ist im Grunde das gleiche wie das im Artikel :
Sie müssen ConcurrencyMode.Multiple
anwenden.
Dies ist, wo die Terminologie ein wenig verwirrend wird, weil es in diesem Fall nicht wirklich "multi-threaded" bedeutet, wie der MSDN-Dokumentstatus. Es bedeutet gleichzeitig . Standardmäßig (einzelne Parallelität) verzögert WCF andere Anforderungen, bis die ursprüngliche Operation abgeschlossen ist. Daher müssen Sie mehrere gleichzeitige Operationen angeben, um überlappende (gleichzeitige) Anforderungen zuzulassen. Ihr SynchronizationContext
garantiert weiterhin, dass nur ein einziger Thread alle Anfragen verarbeitet, also ist es nicht tatsächlich Multi-Threading. Es ist Single-Threaded Concurrency.
Als Nebenbemerkung möchten Sie vielleicht ein anderes SynchronizationContext
mit einer saubereren Shutdown-Semantik betrachten. Das SingleThreadSynchronizationContext
, das Sie gerade verwenden, wird "geklemmt", wenn Sie Complete
aufrufen; Alle async
-Methoden, die sich in await
befinden, werden nie wieder aufgenommen.
Ich habe einen AsyncContext
-Typ , der bessere Unterstützung für sauberes Herunterfahren bietet. Wenn Sie das Nito.AsyncEx NuGet-Paket installieren, können Sie den folgenden Servercode verwenden:
Dadurch wird Ctrl-C in einen "weichen" Exit umgewandelt, was bedeutet, dass die Anwendung so lange ausgeführt wird, wie Clientverbindungen bestehen (oder bis das Zeitlimit überschritten wird). Während des Schließens können bestehende Clientverbindungen neue Anforderungen stellen, neue Clientverbindungen werden jedoch abgelehnt.
Ctrl-Break ist immer noch ein "harter" Ausgang; Sie können das in einem Konsolenhost nicht ändern.
Tags und Links wcf c# async-await asynchronous servicehost