Влияет ли количество процессоров на производительность асинхронных потоков в веб-приложении ASP.NET MVC 4?

Я не так хорош в асинхронном программировании, поэтому вопрос может быть на низком уровне.

Я создал асинхронный метод, как показано ниже, с помощью Async CTP на ASP.NET MVC 4 Dev. Предварительный просмотр:

public class Movie
{
    public string Title { get; set; }
    public string Url { get; set; }
    public string BoxArtUrl { get; set; }
}

public class MovieM {

    public IEnumerable<Movie> M2009 { get; set; }
    public IEnumerable<Movie> M2010 { get; set; }
    public IEnumerable<Movie> M2011 { get; set; }
}

public class HomeController : AsyncController  {

    public async Task<ActionResult> GetMoviesM()  {

        var profiler = MiniProfiler.Current; // it's ok if this is null

        var pageSize = 1000;
        var imageCount = 0;

        using (profiler.Step("Start pulling data (Async) and return it")) { 

            var m2009 = await QueryMoviesAsync(2009, imageCount, pageSize);
            var m2010 = await QueryMoviesAsync(2010, imageCount, pageSize);
            var m2011 = await QueryMoviesAsync(2011, imageCount, pageSize);

            return View(new MovieM { 
                M2009 = m2009,
                M2010 = m2010,
                M2011 = m2011
            });
        }
    }

    XNamespace xa = "http://www.w3.org/2005/Atom";
    XNamespace xd = "http://schemas.microsoft.com/ado/2007/08/dataservices";
    XNamespace xm = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

    string query = "http://odata.netflix.com/Catalog/Titles?$filter=ReleaseYear eq {0}&$skip={1}&$top={2}&$select=Url,BoxArt";

    async Task<IEnumerable<Movie>> QueryMoviesAsync(int year, int first, int count)  {

        var client = new WebClient();
        var url = String.Format(query, year, first, count);
        var data = await client.DownloadStringTaskAsync(new Uri(url));
        var movies =
            from entry in XDocument.Parse(data).Descendants(xa + "entry")
            let properties = entry.Element(xm + "properties")
            select new Movie
            {
                Title = (string)entry.Element(xa + "title"),
                Url = (string)properties.Element(xd + "Url"),
                BoxArtUrl = (string)properties.Element(xd + "BoxArt").Element(xd + "LargeUrl")
            };
        return movies.AsEnumerable();
    }
}

Код работает просто отлично. Когда мы запускаем ту же функцию в настольном приложении (например, в приложении WPF), мы видим ощутимую разницу в производительности. Пользовательский интерфейс не заблокирован, данные сразу же выводятся на экран, когда они доступны.

Но в веб-приложении я действительно не вижу разницы. Я также создал ту же функцию, что и синхронизация, и обе они почти одинаковы.

Что я хотел бы знать, так это то, что:

  1. Я запускаю это приложение на компьютере с процессором Intel Core 2 Duo T5750 2,00 ГГц. Влияет ли количество процессоров на производительность асинхронного потока на С#?
  2. Я делаю что-то не так с точки зрения веб-приложения?

person tugberk    schedule 05.12.2011    source источник
comment
Это становится интереснее, когда вы добавляете больше параллельных запросов: очевидно, что чем меньше состязаний, тем лучше и быстрее каждый из них может работать (меньше переключений контекста, меньше ожидания доступности объединенных потоков перед запуском).   -  person Marc Gravell    schedule 05.12.2011
comment
@MarcGravell Спасибо за ответ, Марк. Итак, вы говорите, что это даст лучшую производительность, если в приложении будет больше запросов. Я на правильном пути здесь?   -  person tugberk    schedule 05.12.2011
comment
@MarcGravell также, вы видите какие-либо плохие места в коде?   -  person tugberk    schedule 05.12.2011
comment
Не читал (я с мобильного), но : да; на распараллеливание сильно влияют параллельные запросы   -  person Marc Gravell    schedule 05.12.2011


Ответы (2)


Когда мы запускаем ту же функцию в настольном приложении (например, в приложении WPF), мы видим ощутимую разницу в производительности. Пользовательский интерфейс не заблокирован, данные сразу же выводятся на экран, когда они доступны.

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

В сценарии ASP.NET страница отображается только после завершения всех асинхронных запросов; вот почему вы не видите разницы.

Вы можете сделать его фактически более производительным, распараллелив свои запросы:

var m2009Task = QueryMoviesAsync(2009, imageCount, pageSize);
var m2010Task = QueryMoviesAsync(2010, imageCount, pageSize);
var m2011Task = QueryMoviesAsync(2011, imageCount, pageSize);
await Task.WhenAll(m2009Task, m2010Task, m2011Task);
var m2009 = await m2009Task;
var m2010 = await m2010Task;
var m2011 = await m2011Task;

Это улучшит производительность как для рабочего стола, так и для ASP.NET.

В исходном коде используется последовательная композиция (по одному await за раз). В этом случае код выполняется асинхронно, но запрос выполняется не быстрее. Преимущество использования async/await в таком виде все же есть: запрос не связывает поток ASP.NET, поэтому ваша служба может больше масштабироваться. В мире пользовательского интерфейса поток пользовательского интерфейса не зацикливается, поэтому он более отзывчив. Но как для ASP.NET, так и для пользовательского интерфейса общее время для завершения метода GetMoviesM не уменьшается, если сделать его async.

Параллельная композиция (с использованием Task.WhenAll или Task.WhenAny) позволяет GetMoviesM в целом работать быстрее, поскольку запросы выполняются параллельно. Кроме того, вы получаете упомянутые выше преимущества резьбы.

При этом количество процессоров не имеет значения. Они вступают в игру только тогда, когда вы выполняете обработку в пуле потоков (например, Task.Run), а не при выполнении ввода-вывода. (Это упрощение, но достаточно верное).

person Stephen Cleary    schedule 07.12.2011
comment
Это то, что я искал. Благодарность! В приведенном выше примере я использую новый VS AsyncCTP. В вашем примере кода вы также выиграли от этого. Например, каким будет код, если нам нужно сделать это по старинке? - person tugberk; 07.12.2011
comment
Вы можете QueryMovies уменьшить (используя блокировку) общий счетчик, инициализированный до 3, и запустить остальную часть метода, когда счетчик достигнет 0. - person Stephen Cleary; 07.12.2011

Несколько комментариев.

Во-первых, WebClient по умолчанию открывает только 2 соединения на сервер за сеанс. Это, очевидно, повлияет на ваши возможности масштабирования, поэтому вы можете изменить это [см. limit-in-webclient">Как я могу программно удалить ограничение на 2 соединения в WebClient ]

Во-вторых, я не уверен, что есть какая-то польза от использования async как в вашем методе контроллера, так и внутри метода QueryMoviesAsync.

В-третьих, WebClient реализует IDisposable, поэтому его следует использовать с оператором using(..). Если этого не сделать, это также может повлиять на масштабируемость.

Учитывая все вышеперечисленные изменения и ответ на ваш первоначальный вопрос, да, код должен масштабироваться на несколько процессоров/ядер во время выполнения, поскольку это значение по умолчанию для ASP.NET/IIS.

person geoffreys    schedule 06.12.2011