2013-06-17 33 views
10

Po zbadaniu koncepcji asynchronicznego tworzenia sieci, szczególnie ze źródła this, stworzyłem przykładową aplikację, aby udowodnić tę koncepcję.ASP.NET C# 5 Asynchroniczne aplikacje internetowe przy użyciu Async & Await

Rozwiązanie składa się z 2 aplikacji ASP.NET Web API. Pierwszy to symulowany powolny punkt końcowy; czeka na 1000 ms przed powrotem listę niestandardowej klasy o nazwie Student:

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" } }; 
    } 

Oto klasa Student:

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

Ten punkt końcowy jest obsługiwana w IIS 7 na localhost: 4002.

Drugi kontakty aplikacji pierwszym użyciu 2 punktów końcowych, jeden synchronicznych drugiej asynchronicznych:

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()); 
    } 

Jest obsługiwana w IIS 7 na localhost: 4001.

Oba punkty końcowe działają zgodnie z oczekiwaniami i wracają za około. 1 sekunda. Na podstawie wideo w powyższym linku o 13:25 metoda asynchroniczna powinna zwolnić wątek, minimalizując rywalizację.

Używam testów wydajności aplikacji przy użyciu Apache Bench. Oto czasy reakcji dla metody synchronicznej z 10 jednoczesnych żądań:

Synchronous Results

Jest to bardzo bym się spodziewać; więcej współbieżnych połączeń zwiększa rywalizację i wydłuża czas odpowiedzi. Jednak tutaj są asynchroniczne czasy reakcji:

Asynchronous Results

Jak widać, nie wydaje się być jeszcze jakiś spór. Spodziewałbym się, że średni czas reakcji będzie bardziej zrównoważony. Jeśli uruchomię testy na obu punktach końcowych z 50 równoczesnymi żądaniami, nadal otrzymam podobne wyniki.

Na tej podstawie wydaje się, że zarówno metody asynchroniczne, jak i synchroniczne działają z mniej więcej taką samą szybkością (oczekiwaną), nie biorąc pod uwagę obciążenia metod asynchronicznych, ale także, że metoda asynchroniczna nie wydaje się zwalniaj wątki z powrotem do ThreadPool. Z zadowoleniem przyjmuję wszelkie uwagi lub wyjaśnienia, dziękuję.

+0

Największą zmianą, jaką otrzymasz, jest metoda polegająca na użyciu zewnętrznych źródeł, takich jak baza danych. – JustAnotherUserYouMayKnow

+0

Dzięki, pomyślałem, że tak było w przypadku długotrwałej prośby, którą właśnie symulowałem. Czy sugerujesz, że struktura asynchroniczna jest zoptymalizowana dla żądań opartych na IO, w przeciwieństwie do CPU-bound? Zmodyfikuję kod tak, aby zawierał żądanie bazy danych. –

+1

Celem asynchronicznej struktury jest uwolnienie Nici, kiedy musi poczekać na odpowiedź, aby w międzyczasie mogła zająć się inną pracą. W większości przypadków dotyczy to żądań opartych na IO. Kiedy zażądasz dużo danych z bazy danych, co powinien nić tymczasem? Poczekaj, aż dane wrócą ... Albo wróć do wątku w międzyczasie i pracuj nad innymi, dopóki dane nie wrócą? – JustAnotherUserYouMayKnow

Odpowiedz

8

Myślę, że istnieje spora szansa, że ​​nie testujesz tego, co myślisz, że testujesz. Z tego, co mogę zebrać, próbujesz wykryć uwolnienia z powrotem do puli wątków, porównując czasy i wnioskowanie o wtryskaniu nici.

Po pierwsze, domyślne ustawienia puli wątków w .NET 4.5 są bardzo wysokie. Nie trafisz ich w 10 lub 100 równoczesnych próśb.

Cofnij się o sekundę i pomyśl o tym, co chcesz przetestować: czy metoda asynchroniczna zwraca swój wątek do puli wątków?

Mam demo, które pokazuję, aby to pokazać. Nie chciałem stworzyć testu ciężkiego obciążenia dla mojego demo (uruchomionego na moim laptopie prezentacji), więc wyciągnąłem małą sztuczkę: sztucznie zawężam pulę wątków do bardziej rozsądnej wartości.

Kiedy to zrobisz, twój test jest dość prosty: wykonaj tyle jednoczesnych połączeń, a następnie przeprowadź wiele takich testów, i jeden. Synchroniczna implementacja będzie musiała poczekać, aż się zakończy, zanim rozpocznie się ostatnia, podczas gdy asynchroniczna implementacja będzie mogła uruchomić je wszystkie.

Po stronie serwera, najpierw ograniczają wątki puli wątków z liczbą procesorów w systemie:

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

następnie wykonaj synchroniczne i asynchroniczne implementacje:

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" }; 
    } 
} 

i wreszcie klienta testowanie kodu:

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); 
} 

Na moim (8-logicznym-rdzeniu) mach ine, widzę wyjście takie jak to:

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 

Co wyraźnie pokazuje, że metoda asynchroniczna zwraca swój wątek do puli wątków.

+0

Dziękuję Stephen, doskonałe wyjaśnienie. Nawiasem mówiąc, kiedy IIS zwraca wątek do ThreadPool podczas żądania asynchronicznego, jaki wątek powoduje, że wynikowy stan-maszyna, który zarządza wywołaniem zwrotnym, działa? Czy jest on hostowany przez IIS, w takim przypadku, czy wykorzystuje także ThreadPool, czy jest uruchamiany w wątkach systemu operacyjnego poza usługami IIS? –

+0

Domyślnie metoda 'async' zostanie wznowiona w kontekście żądania. W rzeczywistości ASP.NET pobierze dowolny dostępny wątek (ze swojej puli wątków) i wątek ten wejdzie w kontekst żądania przed wznowieniem metody. –

+0

Dzięki. Rozumiem, że wątek jest zwracany do puli, dopóki nie zwrócą się oczekiwane metody. Co się tyczy maszyny stanu, która monitoruje oczekiwane żądanie - czy działa w środowisku ASP.NET w wątku? Czy jest on hostowany gdzie indziej? –

Powiązane problemy