Использование Task или async / await в IHttpAsyncHandler - PullRequest
13 голосов
/ 10 февраля 2012

С самого начала написания приложений ASP.NET, когда я хотел добавить многопоточность, есть три простых способа выполнения многопоточности в приложении ASP.NET:

  • Использование System.Threading.ThreadPool,
  • Использование пользовательского делегата и вызов его метода BeginInvoke.
  • Использование пользовательских потоков с помощью класса System.Threading.Thread.

Первые два метода предлагают быстрый способ запуска рабочих потоков для вашего приложения.Но, к сожалению, они снижают общую производительность вашего приложения, поскольку они потребляют потоки из того же пула, который используется ASP.NET для обработки HTTP-запросов .

Затем я захотел использовать новую задачу илиasync / ждут записи IHttpAsyncHandler.Один пример, который вы можете найти, - это то, что Дрю Марш объясняет здесь: https://stackoverflow.com/a/6389323/261950

Я предполагаю, что использование Task или async / await по-прежнему потребляет поток из пула потоков ASP.NET, и я не хочу, чтобыОчевидная причина.

Не могли бы вы сказать , если я могу использовать Task (async / await) в фоновом потоке , как с System.Threading.Thread class , а не из пула потоков ?

Заранее спасибо за помощь.

Томас

Ответы [ 6 ]

16 голосов
/ 10 февраля 2012

Это ситуация, когда Task, async и await действительно сияют.Вот тот же пример, который был переработан для использования всех преимуществ async (он также использует некоторые вспомогательные классы из моей библиотеки AsyncEx для очистки кода отображения):

// First, a base class that takes care of the Task -> IAsyncResult mapping.
// In .NET 4.5, you would use HttpTaskAsyncHandler instead.
public abstract class HttpAsyncHandlerBase : IHttpAsyncHandler
{
    public abstract Task ProcessRequestAsync(HttpContext context);

    IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        var task = ProcessRequestAsync(context);
        return Nito.AsyncEx.AsyncFactory.ToBegin(task, cb, extraData);
    }

    void EndProcessRequest(IAsyncResult result)
    {
        Nito.AsyncEx.AsyncFactory.ToEnd(result);
    }

    void ProcessRequest(HttpContext context)
    {
        EndProcessRequest(BeginProcessRequest(context, null, null));
    }

    public virtual bool IsReusable
    {
        get { return true; }
    }
}

// Now, our (async) Task implementation
public class MyAsyncHandler : HttpAsyncHandlerBase
{
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        using (var webClient = new WebClient())
        {
            var data = await webClient.DownloadDataTaskAsync("http://my resource");
            context.Response.ContentType = "text/xml";
            context.Response.OutputStream.Write(data, 0, data.Length);
        }
    }
}

(какКак отмечается в коде, в .NET 4.5 есть HttpTaskAsyncHandler, который похож на наш HttpAsyncHandlerBase выше).

действительно классная вещь о asyncявляется то, что он не принимает каких-либо потоков при выполнении фоновой операции:

  • Поток запроса ASP.NET запускает запрос и начинает загрузку с использованием WebClient.
  • Пока идет загрузка, await на самом деле возвращает из метода async, оставляя поток запроса.Этот поток запроса возвращается обратно в пул потоков - оставляя 0 ( ноль ) потоков, обслуживающих этот запрос.
  • Когда загрузка завершается, метод async возобновляется в потоке запроса.Этот поток запроса кратко используется только для записи фактического ответа.

Это оптимальное решение для потоков (поскольку поток запроса требуется для записи ответа).

Исходный примертакже оптимально использует потоки - что касается потоков, то это то же самое, что и код async.Но IMO код async легче читать.

Если вы хотите узнать больше о async, у меня есть вступительное сообщение в моем блоге.

7 голосов
/ 24 февраля 2012

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

Факты ASP.NET ThreadPool

  • Как сказал Андрес: Когда async / await не будет использовать дополнительный поток ThreadPool? Только в том случае, если вы используете асинхронные методы BCL. который использует поток IOCP для выполнения связанной операции ввода-вывода.

  • Андрес продолжает с ... Если вы пытаетесь выполнить асинхронный код синхронизации или свой собственный код библиотеки , этот код, вероятно, будет использовать дополнительный поток ThreadPool если вы явно не используете IOCP ThreadPool или свой собственный ThreadPool.

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

  • ASP.NET использует потоки из пула потоков общеязыковой среды выполнения (CLR) для обработки запросов. Если в пуле потоков есть потоки, ASP.NET без проблем отправляет входящие запросы.

  • Async delegates использовать темы из ThreadPool.

Когда вы должны начать думать о реализации асинхронного выполнения?

  • Когда ваше приложение выполняет относительно длительные операции ввода-вывода (запросы к базе данных, вызовы веб-служб и другие операции ввода-вывода)

  • Если вы хотите выполнить работу ввода-вывода, вам следует использовать поток ввода-вывода (порт завершения ввода-вывода), и, в частности, вы должны использовать асинхронные обратные вызовы, поддерживаемые любым классом библиотеки, который вы используете. с помощью. Их имена начинаются со слов Begin и End.

  • Если запросы вычислительно дешевы в обработке, то параллелизм, вероятно, является ненужным дополнительным расходом.

  • Если частота входящих запросов высока, то добавление большего параллелизма, скорее всего, даст мало преимуществ и может фактически снизить производительность, поскольку скорость работы на входе может быть достаточно высокой, чтобы загружать процессоры.

Должен ли я создавать новые темы?

  • Избегайте создания новых тем, как если бы вы избежали чумы.

  • Если вы на самом деле ставите в очередь достаточно рабочих элементов, чтобы предотвратить обработку дальнейших запросов в ASP.NET, то вы должны истощать пул потоков! Если вы выполняете буквально сотни ресурсоемких операций одновременно, что было бы хорошо, если бы другой рабочий поток обслуживал запрос ASP.NET, когда машина уже перегружена.

А TPL?

  • TPL может адаптироваться для использования доступных ресурсов в процессе. Если сервер уже загружен, TPL может использовать всего одного работника и продвигаться вперед. Если сервер в основном свободен, он может использовать столько рабочих, сколько может сэкономить ThreadPool.

  • Задачи используют потоки пула потоков для выполнения.

Ссылки

3 голосов
/ 15 февраля 2012

Сказать, что «0 (ноль) потоков будут обслуживать этот запрос» не совсем точно.Я думаю, что вы имеете в виду «из ASP.NET ThreadPool», и в общем случае это будет правильно.

Когда async / await не будет использовать дополнительный поток ThreadPool?Только в том случае, если вы используете асинхронные методы BCL (например, предоставляемые асинхронными расширениями WebClient), использующие поток IOCP для выполнения связанной с IO операции.

Если вы пытаетесь выполнить асинхронную синхронизацию, выполните какой-либо код синхронизациииз собственного кода библиотеки, этот код, вероятно, будет использовать дополнительный поток ThreadPool, если вы явно не используете IOCP ThreadPool или свой собственный ThreadPool.

Спасибо, Андрес.

1 голос
/ 16 февраля 2012

Еще одна вещь, которую следует учитывать, это то, что async / await и TPL (Task) - это не одно и то же.

Пожалуйста, прочитайте этот отличный пост http://blogs.msdn.com/b/ericlippert/archive/2010/11/04/asynchrony-in-c-5-0-part-four-it-s-not-magic.aspx, чтобы понять, почему async / await не означает "использование фоновой цепочки".

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

1) оставить код внутри Asynchandler, чтобы при дорогих вычислениях использовался текущий поток из ThreadPool.2) запустить дорогой код расчета в другом потоке ThreadPool с помощью Task.Run или Delegate 3) запустить дорогой код вычисления в другом потоке из своего пользовательского пула потоков (или IOCP threadPool).

Второй случай МОЖЕТ быть достаточным для вас в зависимости от того, как долго выполняется ваш «расчетный» процесс и сколько у вас нагрузки.Безопасный вариант №3, но намного дороже в кодировании / тестировании.Я также рекомендую всегда использовать .NET 4 для производственных систем, использующих асинхронный дизайн, потому что в .NET 3.5 есть некоторые жесткие ограничения.

1 голос
/ 16 февраля 2012

Команда Parallel Extensions имеет сообщение в блоге об использовании TPL с ASP.NET, которое объясняет, как TPL и PLINQ используют ASP.NET ThreadPool.В публикации даже есть таблица решений, которая поможет вам выбрать правильный подход.

Короче говоря, PLINQ использует один рабочий поток на ядро ​​из пула потоков для всего выполнения запроса, что может привести к проблемам, есливысокий трафик.

Методы Task и Parallel, с другой стороны, адаптируются к ресурсам процесса и могут использовать для обработки всего один поток.

Что касается Async CTP, концептуальная разница между конструкцией async / await и непосредственным использованием Задач невелика.Компилятор использует некоторую магию для преобразования ожидаемых заданий и продолжений за кулисы.Большая разница в том, что ваш код НАМНОГО чище и проще в отладке.

0 голосов
/ 21 апреля 2012

Есть хорошая реализация HttpTaskAsyncHandler для .NET 4.0 в проекте SignalR. Вы можете отключить его: http://bit.ly/Jfy2s9

...