Подтвердить что ты не робот

IAsyncResult против ThreadPool

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

Почему я не предпочитаю IAsyncResult:

  • Добавлена ​​сложность с BeginXXX и EndXXX
  • Caller может забыть вызывать EndXXX, если он не заботится о возвращаемом значении
  • Увеличение избыточности в дизайне API (нам нужно создать методы обхода Begin и End для всех методов, которые мы хотим запустить асинхронно)
  • Уменьшенная читаемость

Чтобы поместить его в код:

ThreadPool

  public void ThreadPoolApproach()
  {
     ThreadPool.QueueUserWorkItem( ( a ) =>
     {
        WebClient wc = new WebClient();
        var response = wc.DownloadString( "http://www.test.com" );
        Console.WriteLine( response );
     } );
  }

IAsyncResult

  public void IAsyncResultApproach()
  {
     var a = BeginReadFromWeb( ( result ) =>
     {
        var response = EndReadFromWeb( result );
        Console.WriteLine( response );
     }, "http://www.test.com" );
  }

  public IAsyncResult BeginReadFromWeb( AsyncCallback a, string url )
  {
     var result = new AsyncResult<string>( a, null, this, "ReadFromFile" );

     ThreadPool.QueueUserWorkItem( ( b ) =>
     {
        WebClient wc = new WebClient();
        result.SetResult( wc.DownloadString( url ) );
        result.Complete( null );
     } );

     return result;
  }

  public string EndReadFromWeb( IAsyncResult result )
  {
     return AsyncResult<string>.End( result, this, "ReadFromFile" );
  }
4b9b3361

Ответ 1

Нет, есть огромная разница между двумя фрагментами кода. Оба на самом деле используют threadpool, первый делает это явно, конечно. Второй делает это гораздо менее видимым (и сломанным) способом, обратный вызов IAsyncResult выполняется в потоке threadpool.

Threadpool - общий ресурс, в большой программе у вас будет много применений для потоков TP. Не только явно в вашем собственном коде,.NET Framework также использует их. Руководство для типа кода, который работает в потоковом пуле, - это код, который выполняется быстро и не делает никаких блокирующих вызовов, которые помещают поток TP в состояние ожидания. Блокировка использует очень дорогой операционный ресурс очень неэффективным способом, а десны - другим кодом, который может использовать поток TP. Важной частью потокового пула является планировщик, он пытается ограничить количество выполняемых потоков ТТ количеством ядер ЦП, доступным для этого устройства.

Но блокировка - это именно то, что вы делаете в первом фрагменте. WebClient.DownloadString() - очень медленный метод, который не может быть выполнен быстрее, чем ваше интернет-соединение, или сервер на другом конце провода. Фактически, вы занимаете поток TP, возможно, минут. Не делая вообще никакой работы вообще, он постоянно ждет завершения вызова Socket.Read(). Эффективное использование центрального процессора в лучшем случае составляет несколько процентов.

Этот много отличается при использовании метода BeginXxxx() или XxxxAsync(). Он внутренне реализован как часть кода, чтобы попросить операционную систему начать операцию ввода-вывода. Занимает всего несколько микросекунд. ОС передает запрос на драйвер устройства, стек TCP/IP в случае DownloadStringAsync(). Где он будет находиться в качестве элемента данных в очереди запросов ввода-вывода. Ваш звонок очень быстро возвращается.

В конце концов, ваша сетевая карта получает данные с сервера, и драйвер завершает запрос ввода-вывода. Через несколько слоев, которые получают CLR для захвата другого потока TP и выполнения вашего обратного вызова. Вы быстро выполняете все, что вы делаете, с данными, какой-то шаг обработки, который обычно также занимает микросекунды.

Обратите внимание на разницу, ваш первый код занимает поток TP в течение нескольких минут, асинхронная версия связывает потоки в течение микросекунд. Асинхронная версия масштабируется намного лучше, способная обрабатывать многие запросы ввода-вывода.

Значительная проблема с асинхронной версией кода заключается в том, что писать гораздо сложнее. То, что локальные переменные в синхронной версии должны стать полями класса в асинхронной версии. Это также намного сложнее отладить. Именно поэтому .NET получил класс Task, далее расширенный позже с поддержкой ключевых слов async/await на языках С# и VB.NET.

Ответ 2

Отложите естественные асинхронные операции с привязкой к IO, которые не требуют завершения выделенного потока (см. Нет темы Stephen Клири). Не имеет смысла выполнять синхронную версию DownloadString естественно асинхронного DownloadStringAsync API в потоке пула, потому что ваша блокировка драгоценный ресурс напрасно: нить.

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

Начнем с того, что не существует стандартного класса AsyncResult<T> в .NET Framework.. Я полагаю, что реализация AsyncResult<string>, на которую вы ссылаетесь в своем коде, была взята из Параллельные вопросы: реализация модели асинхронного программирования CLR в статье Джеффри Рихтера. Я также считаю, что автор показывает, как реализовать AsyncResult<T> для образовательных целей, иллюстрируя, как может выглядеть реализация CLR. Он выполняет часть работы над потоком пула через ThreadPool.QueueUserWorkItem и реализует IAsyncResult, чтобы предоставить уведомление о завершении. Более подробную информацию можно найти в LongTask.cs, сопроводив статью.

Итак, чтобы ответить на вопрос:

Я действительно задаюсь вопросом, зачем использовать IAsyncResult, когда у нас есть способ лучшая альтернатива ThreadPool?

Это не случай "IAsyncResult vs ThreadPool".. Скорее, в контексте вашего вопроса IAsyncResult дополняет ThreadPool.QueueUserWorkItem, он предоставляет способ уведомлять вызывающего абонента о завершении выполнения рабочего элемента. API-интерфейс ThreadPool.QueueUserWorkItem сам по себе не имеет этой функции, он просто возвращает bool, указывающий, был ли рабочий элемент успешно поставлен в очередь для асинхронного выполнения в потоке пула.

Однако для этого сценария вам не нужно реализовывать AsyncResult<T> или использовать ThreadPool.QueueUserWorkItem вообще. Рамка позволяет асинхронно выполнять делегирование на ThreadPool и отслеживать статус завершения, просто используя метод делегата BeginInvoke. То, как Framework реализует шаблон шаблон асинхронной модели программирования (APM) для делегатов. Например, здесь вы можете выполнить некоторую работу с ЦП с использованием BeginInvoke:

static void Main(string[] args)
{
    Console.WriteLine("Enter Main, thread #" + Thread.CurrentThread.ManagedThreadId);

    // delegate to be executed on a pool thread
    Func<int> doWork = () =>
    {
        Console.WriteLine("Enter doWork, thread #" + Thread.CurrentThread.ManagedThreadId);
        // simulate CPU-bound work
        Thread.Sleep(2000);
        Console.WriteLine("Exit doWork");
        return 42;
    };

    // delegate to be called when doWork finished
    AsyncCallback onWorkDone = (ar) =>
    {
        Console.WriteLine("enter onWorkDone, thread #" + Thread.CurrentThread.ManagedThreadId);
    };

    // execute doWork asynchronously on a pool thread
    IAsyncResult asyncResult = doWork.BeginInvoke(onWorkDone, null); 

    // optional: blocking wait for asyncResult.AsyncWaitHandle
    Console.WriteLine("Before AsyncWaitHandle.WaitOne, thread #" + Thread.CurrentThread.ManagedThreadId);
    asyncResult.AsyncWaitHandle.WaitOne();

    // get the result of doWork
    var result = doWork.EndInvoke(asyncResult);
    Console.WriteLine("Result: " + result.ToString());

    // onWorkDone AsyncCallback will be called here on a pool thread, asynchronously 
    Console.WriteLine("Press Enter to exit");
    Console.ReadLine();
}

Наконец, стоит упомянуть, что шаблон APM заменяется гораздо более удобным и хорошо структурированным Асинхронным шаблоном (TAP) на основе задач. рекомендуется, чтобы шаблон TAP был предпочтительнее других, более низкоуровневых API.

Ответ 3

В основном эти два способа - это просто другое поведение одного и того же. Одной из причин использования IAsyncResult над ThreadPool является возвращаемое значение: Threading.WaitCallback возвращает void, поэтому вы не можете возвращать какое-либо значение непосредственно вызовом ThreadPool.QueueUserWorkItem, но вы можете использовать метод IAsyncResult.