2013-06-17 2 views
10

Nachdem ich das Konzept der asynchronen Web-Entwicklung, speziell von this Quelle erforscht, habe ich eine Beispielanwendung erstellt, um das Konzept zu beweisen.ASP.NET C# 5 asynchrone Web-Anwendungen mit Async & warten

Die Lösung besteht aus 2 ASP.NET Web API-Anwendungen. Der erste ist ein simulierter langsamer Endpunkt; es wartet auf 1000 ms, bevor eine Liste eine benutzerdefinierte Klasse namens Studenten Rückkehr:

public IEnumerable<Student> Get() 
    { 
     Thread.Sleep(1000); 
     return new List<Student> { new Student { Name = @"Paul" }, new Student { Name = @"Steve" }, new Student { Name = @"Dave" }, new Student { Name = @"Sue" } }; 
    } 

Hier ist der Student-Klasse:

public class Student 
{ 
    public string Name { get; set; } 
} 

Dieser Endpunkt in IIS 7 auf localhost gehostet wird: 4002.

der zweite Anwendung in Kontakt mit der ersten unter Verwendung von 2-Endpunkten, eine synchronen, asynchronen die anderen:

public IEnumerable<Student> Get() { 
     var proxy = WebRequest.Create(@"http://localhost:4002/api/values"); 

     var response = proxy.GetResponse(); 
     var reader = new StreamReader(response.GetResponseStream()); 

     return JsonConvert.DeserializeObject<IEnumerable<Student>>(reader.ReadToEnd()); 
    } 

    public async Task<IEnumerable<Student>> Get(int id) { 
     var proxy = new HttpClient(); 
     var getStudents = proxy.GetStreamAsync(@"http://localhost:4002/api/values"); 

     var stream = await getStudents; 
     var reader = new StreamReader(stream); 

     return JsonConvert.DeserializeObject<IEnumerable<Student>>(reader.ReadToEnd()); 
    } 

es in IIS 7 auf localhost gehostet: 4001.

Beide Endpunkte funktionieren wie erwartet und kehren in ca. 1 Sekunde. Basierend auf dem Video im obigen Link um 13:25 Uhr sollte die asynchrone Methode ihren Thread freigeben, wodurch Konflikte minimiert werden.

Ich führe Leistungstests für die Anwendung mit Apache Bench durch. Hier sind die Antwortzeiten für die synchrone Methode mit 10 gleichzeitigen Anfragen:

Synchronous Results

So viel ist, wie ich erwarten würde; Mehr gleichzeitige Verbindungen erhöhen die Konkurrenz und verlängern die Antwortzeiten. Allerdings sind hier die asynchronen Antwortzeiten:

Asynchronous Results

Wie Sie sehen können, scheint es noch einige Anstoßes. Ich hätte erwartet, dass die durchschnittlichen Antwortzeiten ausgewogener wären. Wenn ich die Tests an beiden Endpunkten mit 50 gleichzeitigen Anfragen durchführe, bekomme ich immer noch ähnliche Ergebnisse.

Basierend darauf scheint es, dass sowohl asynchrone als auch synchrone Methoden mit mehr oder weniger der gleichen Geschwindigkeit (erwartet) laufen, ohne den Overhead in asynchronen Methoden zu berücksichtigen, aber auch, dass die asynchrone Methode nicht scheint Teile Threads zurück zum ThreadPool. Ich würde alle Kommentare oder Klarstellungen begrüßen, danke.

+0

Die größte Änderung, die Sie erhalten, ist, wenn Ihre Methoden auf externe Quellen angewiesen sind, z. B. Datenbank. – JustAnotherUserYouMayKnow

+0

Danke, ich hätte gedacht, dass dies der Fall für eine lang andauernde Anfrage war, was ich simuliert habe. Schlagen Sie vor, dass das async-Framework für IO-basierte Anforderungen optimiert ist, im Gegensatz zu CPU-gebundenen? Ich werde den Code so ändern, dass er eine Datenbankanforderung enthält. –

+1

Der Sinn des async-Frameworks ist es, den Thread freizugeben, wenn er auf eine Antwort warten muss, damit er sich währenddessen anderen Aufgaben widmen kann. Dies ist die meiste Zeit für IO-basierte Anfragen der Fall. Wenn Sie viele Daten von der Datenbank anfordern, was sollte der Thread in der Zwischenzeit tun? Warte einfach, bis die Daten wieder da sind ... Oder gehe zurück zum threadpool und arbeite an anderen Sachen, bis die Daten zurück sind? – JustAnotherUserYouMayKnow

Antwort

8

Ich denke, es gibt eine ziemlich gute Chance, dass Sie nicht testen, was Sie denken, dass Sie testen. Soweit ich feststellen kann, versuchen Sie, Releases zurück in den Thread-Pool zu finden, indem Sie Timings vergleichen und die Thread-Injektion ableiten.

Zum einen sind die Standardeinstellungen für den Threadpool auf .NET 4.5 extrem hoch. Sie werden sie nicht mit nur 10 oder 100 gleichzeitigen Anfragen treffen.

Schritt für Schritt zurück und denken Sie an das, was Sie testen möchten: gibt eine async-Methode ihren Thread zum Thread-Pool zurück?

Ich habe eine Demo, die ich zeige, um dies zu demonstrieren. Ich wollte keinen schweren Belastungstest für meine Demo (auf meinem Präsentations-Laptop) erstellen, also habe ich einen kleinen Trick gezogen: Ich beschränke den Thread-Pool künstlich auf einen vernünftigeren Wert.

Sobald Sie das tun, ist Ihr Test ziemlich einfach: Führen Sie so viele gleichzeitige Verbindungen aus, und führen Sie dann so viele plus eine. Die synchrone Implementierung muss warten, bis eine abgeschlossen ist, bevor die letzte ausgeführt wird, während die asynchrone Implementierung sie alle starten kann.

Auf der Serverseite, beschränken ersten Thread-Pool-Threads, die der Anzahl der Prozessoren im System:

protected void Application_Start() 
{ 
    int workerThreads, ioThreads; 
    ThreadPool.GetMaxThreads(out workerThreads, out ioThreads); 
    ThreadPool.SetMaxThreads(Environment.ProcessorCount, ioThreads); 
    ... 
} 

Dann werden die Synchron- und Asynchron-Implementierungen:

public class ValuesController : ApiController 
{ 
    // Synchronous 
    public IEnumerable<string> Get() 
    { 
     Thread.Sleep(1000); 
     return new string[] { "value1", "value2" }; 
    } 

    // Asynchronous 
    public async Task<IEnumerable<string>> Get(int id) 
    { 
     await Task.Delay(1000); 
     return new string[] { "value1", "value2" }; 
    } 
} 

Und schließlich der Kunde Testcode:

static void Main(string[] args) 
{ 
    try 
    { 
     MainAsync().Wait(); 
    } 
    catch (Exception ex) 
    { 
     Console.WriteLine(ex); 
    } 

    Console.ReadKey(); 
} 

static async Task MainAsync() 
{ 
    ServicePointManager.DefaultConnectionLimit = int.MaxValue; 

    var sw = new Stopwatch(); 
    var client = new HttpClient(); 
    var connections = Environment.ProcessorCount; 
    var url = "http://localhost:35697/api/values/"; 

    await client.GetStringAsync(url); // warmup 
    sw.Start(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Synchronous time for " + connections + " connections: " + sw.Elapsed); 

    connections = Environment.ProcessorCount + 1; 

    await client.GetStringAsync(url); // warmup 
    sw.Restart(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Synchronous time for " + connections + " connections: " + sw.Elapsed); 

    url += "13"; 
    connections = Environment.ProcessorCount; 

    await client.GetStringAsync(url); // warmup 
    sw.Restart(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Asynchronous time for " + connections + " connections: " + sw.Elapsed); 

    connections = Environment.ProcessorCount + 1; 

    await client.GetStringAsync(url); // warmup 
    sw.Restart(); 
    await Task.WhenAll(Enumerable.Range(0, connections).Select(i => client.GetStringAsync(url))); 
    sw.Stop(); 
    Console.WriteLine("Asynchronous time for " + connections + " connections: " + sw.Elapsed); 
} 

Auf meinem (8-logischen Kern) mach ine, ich sehe Ausgabe wie folgt:

Synchronous time for 8 connections: 00:00:01.0194025 
Synchronous time for 9 connections: 00:00:02.0362007 
Asynchronous time for 8 connections: 00:00:01.0413737 
Asynchronous time for 9 connections: 00:00:01.0238674 

die deutlich zeigt, dass das asynchrone Verfahren seinen Thread zu dem Thread-Pool zurückkehrt.

+0

Danke Stephen, hervorragende Erklärung. Übrigens, wenn IIS den Thread während einer asynchronen Anfrage an den ThreadPool zurückgibt, auf welchem ​​Thread läuft der resultierende Zustandsautomat, der den Callback verwaltet,? Wird es von IIS gehostet, in welchem ​​Fall nutzt es auch das ThreadPool oder wird es auf OS-Threads außerhalb von IIS ausgeführt? –

+0

Standardmäßig wird eine Async-Methode im Anforderungskontext fortgesetzt. Was tatsächlich passiert, ist, dass ASP.NET jeden verfügbaren Thread (aus seinem Thread-Pool) greift und dieser Thread in den Anfragekontext eintritt, bevor die Methode fortgesetzt wird. –

+0

Danke. Ich verstehe, dass der Thread in den Pool zurückgegeben wird, bis die erwarteten Methoden zurückgegeben werden. Was die Zustandsmaschine betrifft, die die erwartete Anfrage überwacht - läuft sie im Kontext von ASP.NET auf dem ThreadPool? Oder ist es anderswo gehostet? –

Verwandte Themen