2013-05-21 4 views
12

Ich habe eine mehrschichtige C# MVC4-Webanwendung in einem stark frequentierten Szenario, das Dependency Injection für verschiedene Repositorys verwendet. Dies ist sehr nützlich, da es leicht testbar ist, und in der Produktion können wir die Repositories für bestimmte Controller leicht konfigurieren. Alle Controller erben von AsyncController, so dass Aktionsmethoden, die Task<JsonResult> oder Task<ActionResult> zurückgeben, wirklich unserem Server helfen, mit mehr Benutzern und solve for the dreaded thread starvation problem zu skalieren. Einige Repositories verwenden Webdienste, die wir definitiv mit Async/Await nutzen können, um Skalierbarkeit zu bieten. In anderen Fällen können einige nicht verwaltete Dienste verwenden, die nicht sicher verlegt werden können und bei denen async/await keine Vorteile bietet. Jedes Repository ist eine Implementierung einer Schnittstelle, IRepository. Kurz gesagt, jede Implementierung unterscheidet sich drastisch in der Art, wie sie Daten erhalten kann. Diese Konfiguration wird zum Zeitpunkt der Bereitstellung mit web.config-Änderungen und Autofac-Modulen ausgewählt.Async/Warte in mehrschichtigen C# -Anwendungen

Was sind einige der empfohlenen Wege zur Implementierung von async/await in einer Anwendung wie dieser? Um das Muster in meiner vorhandenen Anwendung anzupassen, muss ich die Schnittstelle ändern, um Task<MyBusinessObject> zurückzugeben. Aber ist das nicht mehr ein Implementierungsdetail? Ich könnte zwei Methoden Stubs zur Verfügung stellen, GetData und GetDataAsync, aber für mich würde das die Flexibilität nicht erlauben, die ich jetzt mit IoC-Frameworks wie Autofac habe, wo ich leicht alles aus tauschen kann, ohne Code ändern zu müssen.

Einer der Microsoft-Entwickler für Async/Await hat einen Blog-Beitrag veröffentlicht, "Should I expose an asynchronous wrapper for my synchronous methods?", der in das Problem eingeht. Wenn Ihre asynchrone Methode nicht wirklich asynchron ist (dh native E/A-Vorteile bietet), wird nur ein Overhead hinzugefügt. Mit anderen Worten, es bietet keine Skalierbarkeitsvorteile.

Ich möchte nur, wie der Rest der Gemeinschaft dieses Problem angegangen ist.

+2

'async' und' warten' sind nur Abstraktionen, um eine Blockierungsmethode in eine nicht blockierende Methode umzuwandeln. Sie können dies an einer beliebigen Stelle in Ihrem Code durchführen, ohne die Schnittstelle zu ändern, es sei denn, Sie möchten erzwingen, dass die Schnittstellenmethode immer asynchron ist. –

+1

Wenn Sie die Aufgabe während des gesamten Aufruf-Stacks nicht haben, wird sie NIE asynchron aufgerufen. Man müsste also in die Schnittstelle setzen, um immer eine Task zurückzugeben. –

+1

Das habe ich nicht gesagt. Was ich gesagt habe, ist, dass Sie jede Methode in eine asynchrone Methode verwandeln können, indem Sie 'async' und' await' verwenden, einschließlich einer der Blockierungsmethoden, die Sie bereits auf Ihren Schnittstellen haben. Ein 'Task ' Rückgabetyp ist bei der ursprünglichen Methode nicht erforderlich. Es ist natürlich eine vollkommen gültige Sache, all Ihre Schnittstellenmethoden durch ihre Async-Entsprechungen zu ersetzen, aber es ist nicht erforderlich. –

Antwort

14

In Ihrer Situation, empfehle ich Ihre Schnittstellen asynchron zu machen:

public interface IMyInterface 
{ 
    Task<TMyResult> GetDataAsync(); 
} 

Für synchrone Methoden können Sie Task.FromResult zurück:

public class MyImplementation : IMyInterface 
{ 
    public Task<TMyResult> GetDataAsync() 
    { 
    TMyResult ret = ...; 
    return Task.FromResult(ret); 
    } 
} 

Asynchrone Methoden natürlich async und await verwenden werden können.

In Bezug auf die Implementierungsdetails würde ich "Ja und Nein" sagen. ;) In dieser Hinsicht ist es IDisposable sehr ähnlich. Logischerweise ist es ein Implementierungsdetail in dem Sinne, dass es keine Rolle spielt, in welcher Weise es implementiert wird. Es ist nicht ein Implementierungsdetail in dem Sinne, dass der aufrufende Code anders damit umgehen muss. Ich stimme also zu, dass dies logischerweise ein Implementierungsdetail ist, aber dass die .NET-Plattform nicht ausreichend fortgeschritten ist, um sie als solche zu behandeln.

Alternativ können Sie daran denken, wie diese (die Erklärung ist, ich in der Regel geben): async ist die tatsächliche Umsetzung Detail, und wenn Sie Task<T> statt T zurückkehren, sind definieren Sie eine Schnittstelle, wo die Umsetzung kann asynchron sein.

Ich habe eine series of blog posts, die Adressierung mit async "in der großen" (d. H. Anpassung async in mit OOP, anstatt zu ermöglichen, dass es funktionsfähig ist). Ich behandle einige allgemeine Probleme wie asynchrone Initialisierung und IoC. Es ist nur die Meinung einer Person, aber mir sind keine anderen Ressourcen für diese Art von Problemen bekannt.

P.S. Sie müssen in MVC4 nicht mehr von AsyncController erben. Der Standard-Controller-Handler erkennt automatisch async Methoden anhand ihres Rückgabetyps (Task oder Task<T>).

+0

Der Link zu Ihrer Reihe von Blogposts scheint unterbrochen zu sein. Könnten Sie aktualisieren? – fourpastmidnight

+0

Macht nichts, Chrome war dumm und hat den Link nicht richtig übersetzt. Danke fürs Schreiben. – fourpastmidnight

+0

Tatsächlich war die Verbindung unterbrochen; Ich habe Anfang dieses Jahres den Anbieter gewechselt. Es ist jetzt behoben, danke! –

1

Aufgabe ist eine undichte Abstraktion. Wenn Sie die Verwendung von IO-Completion-Port-Threads nutzen möchten, müssen Sie Tasks vollständig von Ihren Repositorys zu Ihren Controllern zurückgeben.

Obwohl die gesamte Call-Stack-Rückgabe Task<T> die Skalierbarkeit verbessern kann, fügt sie Ihrer Anwendung eine zusätzliche Komplexitätsebene hinzu. Dies behindert die Wartbarkeit und Testbarkeit und erschwert das Nachdenken über Code. Asynchronous code will always be harder than synchronous code.

Persönlich, ich eher die Menge an Speicher des Servers erhöhen und konfigurieren Sie die Anwendung einen größeren Threadpool zu haben, um den Verlust der Skalierbarkeit zu kompensieren, oder skalieren Sie auf ein paar zusätzliche (virtuelle) Server, anstatt dieses zusätzliche hinzuzufügen Komplexität.

+2

Ich stimme nicht in Bezug auf die zusätzliche Ebene der Komplexität. Wartbarkeit, Testbarkeit und Code-Argumentation sind einfach mit "async"/"erwarten" (sobald Sie sie verstehen). –