2013-02-14 14 views
45

Ich stehe vor dem Problem, Methoden zu entwerfen, die mit Netzwerk-I/O (für eine wiederverwendbare Bibliothek) durchführt. Ich habe diese Frage lesenSchreiben Sie eine gut gestaltete asynchrone/nicht-asynchrone API

c# 5 await/async pattern in API design

und auch andere, die näher an mein Problem.

Also, die Frage ist, wenn ich sowohl async als auch nicht-async Methode zur Verfügung stellen möchte, wie ich diese zu entwerfen? es ist nicht ein großer Entwurf

Zum Beispiel einer nicht-Asynchron-Version eines Verfahrens auszusetzen, ich brauche so etwas wie

public void DoSomething() { 
    DoSomethingAsync(CancellationToken.None).Wait(); 
} 

und ich fühle mich zu tun. Ich hätte gerne einen Vorschlag (zum Beispiel), wie man private Methoden definieren kann, die in öffentliche eingebunden werden können, um beide Versionen zur Verfügung zu stellen.

+3

Wichtige Frage: Wie ist der Async-Teil von 'DoSomethingAsync' implementiert? Dies ist wichtig, denn wenn Sie einen "Task" und einen Worker-Thread erstellen, kann die Antwort sehr unterschiedlich sein, wenn Sie beispielsweise eine externe Event-API verwenden. Insbesondere hat es keinen Sinn, wenn die "sync" -Methode die "async" -Methode und "wait" aufruft, wenn die "async" -Methode einen Thread verwendet: Es wäre besser, die Arbeit direkt zu machen. Dies ändert sich jedoch für andere Bedeutungen von "async" (nicht alle "async" bedeutet "Threads") –

+0

@MarcGravell, versuche ich besser zu erklären (und es ist nicht einfach, weil Englisch nicht meine primäre Sprache ist). Ich habe "Operation XYZ", die intensive I/O machen. Ich möchte es in einer Bibliothek mit zwei Methoden verfügbar machen: eine, die fast sofort mit einem asynchronen Muster zurückkehrt (wie beschrieben in _MS taskbasiertes asynchrones Muster doc_) und eines, das synchron ausführt. Was ist der ** effizient **/** wartbare **/** Unit-Test aktiviert ** Weg dazu. Eine private Methode, die mit einem öffentlichen Async-Wrapper und einem anderen Sync-Wrapper, z. Wenn ja, wie sehen diese Methoden aus? – jay

+2

"Effizient" und "Wartungsfähig" sind nicht dasselbe und stimmen oft nicht überein. Und noch einmal: Alles hängt davon ab, wie Ihr "Async" -Code derzeit funktioniert. Wenn das prozeduraler/linearer Code ist, den Sie gerade in einem separaten Thread ausführen, dann gibt es keinen Grund, ihn über "async" offenzulegen. Wenn dieser Code vollständig asynchron ist, ist es möglicherweise am besten, zwei vollständig getrennte Implementierungen zu verwenden, wenn Sie "effizient" als Ziel haben möchten. Entschuldigung, aber diese Frage ist ** sehr kontextspezifisch und du hast nicht viel Kontext angegeben. –

Antwort

47

Wenn Sie die wartungsfreundlichste Option haben möchten, stellen Sie nur eine async API bereit, die implementiert wird, ohne blockierende Aufrufe auszuführen oder Thread-Thread-Threads zu verwenden.

Wenn Sie sowohl async als auch synchrone APIs haben möchten, stoßen Sie auf ein Wartungsproblem. Sie müssen es wirklich zweimal implementieren: einmal async und einmal synchron. Beide dieser Methoden werden fast identisch aussehen, so dass die anfängliche Implementierung einfach ist, aber Sie werden mit zwei separaten nahezu identischen Methoden enden, so dass die Wartung problematisch ist.

Insbesondere gibt es keine gute und einfache Möglichkeit, nur einen async oder synchronen "Wrapper" zu machen. Stephen Toub hat die besten Infos zum Thema:

  1. Should I expose asynchronous wrappers for synchronous methods?
  2. Should I expose synchronous wrappers for asynchronous methods?

(die kurze Antwort auf beide Fragen lautet "nein")

+2

{+1} Dies war die Sache, die ich _ needed_ zu hören! In der Zwischenzeit habe ich die Methoden neu geschrieben, um wirklich async zu sein und mich nicht mehr um die Sync-Version zu kümmern. Nachdem alle Verbraucher DoSomethingAsync.Wait() ** von ihrem Code ** aufrufen können, wenn sie es tun möchten. Ich habe mich mit Mono-Kompatibilität beschäftigt, aber Profil 3.0 unterstützt 'async' /' await'-Schlüsselwörter, daher existiert das Problem nicht. Übrigens, tolle Artikel. – jay

+2

@jay Ich gehe weiter als +1; Ich sitze gerade in einer Session mit Stephen Toub, und diese Antwort ist eine ideale Antwort auf eine sehr komplexe Frage. Also gehe ich mit einem +500 raus. –

+0

@MarcGravell: Danke! Sie würden nicht zufällig Stephens Sitzung aufnehmen, oder? :) Ich komme fast nie auf diese Seite des Landes ... –

2

ich sowohl mit Marc und Stephen zustimmen (Cleary).

(BTW, begann ich dies als Kommentar zu Stephen's Antwort zu schreiben, aber es stellte sich heraus, zu lang zu sein; lassen Sie mich wissen, wenn es OK ist, dies als Antwort zu schreiben oder nicht, und fühlen Sie sich frei, Bits zu nehmen daraus und fügen Sie es Stephen's Antwort, im Geiste der "Bereitstellung der besten Antwort").

Es hängt wirklich "ab": wie Marc sagte, ist es wichtig zu wissen, wie DoSomethingAsync asynchron ist. Wir sind uns alle einig, dass es keinen Sinn macht, wenn die "sync" -Methode die "async" -Methode aufruft und "wartet": Dies kann im Benutzercode geschehen. Der einzige Vorteil einer separaten Methode besteht darin, tatsächliche Leistungsgewinne zu erzielen, eine Implementierung zu haben, die unter der Haube anders ist und auf das synchrone Szenario zugeschnitten ist. Dies trifft insbesondere zu, wenn die "async" -Methode einen Thread erstellt (oder aus einem threadpool): Sie haben am Ende etwas, das darunter zwei "Kontrollflüsse" verwendet, während "vielversprechend" mit seinen synchronen Looks in der ausgeführt wird Kontext der Anrufer. Dies kann je nach Implementierung sogar Nebenläufigkeitsprobleme haben.

Auch in anderen Fällen, wie die intensive I/O, die das OP erwähnt, kann es sich lohnen, zwei verschiedene Implementierungen zu haben.Die meisten Betriebssysteme (Windows für sicher) haben für I/O unterschiedliche Mechanismen, die auf die beiden Szenarien zugeschnitten sind: Async-Ausführung und I/O-Operation bringt große Vorteile von OS-Level-Mechanismen wie I/O-Completion-Ports Overhead (nicht signifikant, aber nicht null) im Kernel (schließlich müssen sie Buchhaltung, Versand, etc.) und direktere Implementierung für synchrone Operationen. Die Code-Komplexität variiert auch sehr, besonders bei Funktionen, bei denen mehrere Operationen ausgeführt/koordiniert werden.

Was ich tun würde, ist:

  • haben einige Beispiele/Test für typische Verwendung und Szenarien
  • sehen, welche API-Variante verwendet wird, wo und zu messen. Messen Sie auch den Leistungsunterschied zwischen einer "reinen Sync" -Variante und "Sync". (nicht für die gesamte API, sondern für ausgewählte wenige typische Fälle)
  • basierend auf Messung, entscheiden, ob die zusätzlichen Kosten es wert ist.

Dies vor allem, weil zwei Ziele irgendwie im Gegensatz zueinander stehen. Wenn Sie wartbaren Code möchten, ist es naheliegend, die Synchronisation in async/wait (oder anders herum) zu implementieren (oder, noch besser, nur die async-Variante bereitzustellen und den Benutzer warten zu lassen); Wenn Sie Leistung wünschen, sollten Sie die beiden Funktionen unterschiedlich implementieren, um verschiedene zugrunde liegende Mechanismen (vom Framework oder vom Betriebssystem) auszunutzen. Ich denke, dass es aus Sicht des Komponententests keinen Unterschied machen sollte, wie Sie Ihre API tatsächlich implementieren.

+0

Es gibt einen leichten Overhead für die asynchrone Programmierung, aber es ist alles auf der Seite des Benutzermodus. Der Windows-Kernel ist zu 100% asynchron; Es gibt keine synchronen Aufrufe auf Gerätetreiberebene. –

+0

{+1} @ dema80 Ihre Antwort auf interessante Überlegungen zum Betriebssystem. Ich würde auch gerne hören, was auf niedrigem Niveau in * nix/Mono (für intensive I/O) passiert. Eine andere Sache, die hinzugefügt werden könnte, ist relativ zu meinem letzten Kommentar. Kann der Code berücksichtigt und dann wiederverwendet werden, um zwei wirklich unterschiedliche Implementierungen zu haben? Ohne Umhüllung und wie Sie gesagt haben, beide Versionen ansprechen. Dies ist keine ganztägige Programmierung, sondern eine Framework-Design-Frage. Schließlich hat BCL eine Menge API mit sync/async-Version nebeneinander. – jay

+0

@StephenCleary natürlich wird kein moderner Kernel synchron blockieren: und nach, auf niedriger Ebene ist I/O ein asynchroner Job (Interrupts, IPCs, ...). Dies ist nicht, was ich meinte, ich habe nur darauf hingewiesen, dass die Implementierung für synchrone Funktionen wahrscheinlich direkter ist: Ich bin mir nicht sicher, alles im Benutzermodus zu passieren: Ich denke, dass der Thread mit synchroner I/O das IRP aufbaut, Speichern Sie es im Gerätestapel und warten Sie im Kernel, bis der IRP abgeschlossen ist. Bei asynchroner E/A ist APC oder zusätzliche Warteschlangen erforderlich. Aber dann habe ich in der Windows 2000-Ära daran gearbeitet, die Dinge könnten sich geändert haben! –

Verwandte Themen