2013-06-17 2 views
10

Изучив концепцию асинхронной веб-разработки, в частности от источника this, я создал образец приложения, чтобы доказать концепцию.ASP.NET C# 5 Асинхронные веб-приложения с использованием Async & Await

Решение состоит из 2 приложений ASP.NET Web API. Первая - имитированная медленная конечная точка; он ждет 1000 мс перед возвратом список пользовательского класса под названием 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" } }; 
    } 

Вот класс Student:

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

Эта конечная точка размещается в IIS 7 на локальном хосте: 4002.

Вторых контакты приложений первое использование 2 конечных точек, один синхронного, асинхронные с другой:

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

Это размещенные в IIS 7 на локальном хосте: 4001.

Оба оконечных устройства работают должным образом и возвращаются прибл. 1 секунда. Основываясь на видео в ссылке выше в 13:25, асинхронный метод должен освободить его Thread, сводя к минимуму конкуренцию.

Я выполняю тесты производительности приложения с помощью Apache Bench. Вот время отклика для синхронного метода с 10 одновременных запросов:

Synchronous Results

Это больше, чем я ожидал; более параллельные соединения увеличивают конфликт и продлевают время отклика. Однако, здесь асинхронное время отклика:

Asynchronous Results

Как вы можете видеть, все еще кажется, что некоторые разногласия. Я ожидал, что среднее время отклика будет более сбалансированным. Если я запускаю тесты на обеих конечных точках с 50 одновременными запросами, я все равно получаю аналогичные результаты.

Исходя из этого, кажется, что оба асинхронных и синхронных метода работают с большей или меньшей скоростью (ожидается), не принимая во внимание накладные расходы в асинхронных методах, но также и то, что асинхронный метод не кажется выпустить Threads обратно в ThreadPool. Я бы приветствовал любые комментарии или пояснения, спасибо.

+0

Самое большое изменение, которое вы получите, это когда ваши методы зависят от внешних источников, таких как база данных. – JustAnotherUserYouMayKnow

+0

Спасибо, я бы подумал, что это было так для любого долговременного запроса, который я имитировал. Вы предполагаете, что асинхронная инфраструктура оптимизирована для запросов на основе IO, в отличие от привязки к процессору? Я изменю код, чтобы включить запрос базы данных. –

+1

Точка async-framework - это выпуск Thread, когда ему нужно ждать ответа, поэтому он может позаботиться о другой работе. Это больше всего времени для запросов на основе ввода-вывода. Когда вы запрашиваете много данных из базы данных, каков должен быть поток? Подождите, пока данные вернутся ... Или вернитесь в threadpool тем временем и работайте над другим материалом, пока данные не вернутся? – JustAnotherUserYouMayKnow

ответ

8

Я думаю, что есть очень хороший шанс, что вы не тестируете то, что, по вашему мнению, испытываете. Из того, что я могу собрать, вы пытаетесь обнаружить выпуски обратно в пул потоков, сравнивая тайминги и вызывая впрыскивание потоков.

С одной стороны, настройки по умолчанию для пула потоков на .NET 4.5 чрезвычайно высоки. Вы не собираетесь ударить их всего 10 или 100 одновременных запросов.

Шаг назад на секунду и подумайте о том, что вы хотите проверить: ли метод async возвращает свою нить в пул потоков?

У меня есть демоверсия, которую я показываю, чтобы продемонстрировать это. Я не хотел создавать тяжелую нагрузку для своей демонстрации (работает на моем ноутбуке для презентаций), поэтому я сделал небольшой трюк: я искусственно ограничил пул потоков более разумным значением.

Как только вы это сделаете, ваш тест достаточно прост: выполните много одновременных соединений, а затем выполните это много плюс один. Синхронная реализация должна будет дождаться завершения до начала последнего, в то время как асинхронная реализация сможет запустить их все.

На стороне сервера, первый ограничить поток пула потоков для числа процессоров в системе:

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

Затем сделать синхронные и асинхронные реализации:

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

И, наконец клиента код испытания:

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

На моем (8-логическом ядре) mach ине, я вижу выход, как это:

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 

который ясно показывает, что асинхронный метод возвращает его нить в пул потоков.

+0

Спасибо Стивен, превосходное объяснение. Кстати, когда IIS возвращает Thread в ThreadPool во время асинхронного запроса, какие потоки Thread (s) запускают результирующий State-machine, который управляет обратным вызовом? Является ли он размещенным IIS, и в этом случае он также использует ThreadPool, или он запускается в потоках ОС за пределами IIS? –

+0

По умолчанию метод 'async' будет возобновлен в контексте запроса. Фактически происходит то, что ASP.NET будет захватывать любой доступный поток (из его пула потоков), и этот поток войдет в контекст запроса, прежде чем возобновлять этот метод. –

+0

Спасибо. Я понимаю, что поток возвращается в пул до тех пор, пока не вернутся ожидаемые методы. Что касается конечного автомата, который отслеживает ожидаемый запрос - выполняется ли он в контексте ASP.NET на ThreadPool? Или он размещен в другом месте? –

Смежные вопросы