Ответ 1
ThreadPool
фактически поддерживает два под-пула: один для рабочих потоков и другой для потоков IOCP. Когда доступен результат GetAsync
, случайный поток IOCP (порт завершения ввода/вывода) распределяется из пула для обработки завершения асинхронного HTTP-запроса. То, где выполняется код после await
. У вас есть контроль над размером каждого под-пула с ThreadPool.SetMinThreads/SetMaxThreads
, подробнее об этом ниже.
Как и ваш, не-асинхронный код вряд ли можно сравнить с асинхронной версией. Для более справедливого сравнения вы должны придерживаться WebRequest
для обоих случаев, например:
Non-Асинхронный:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace NoAsync
{
internal class Program
{
private const int totalCalls = 100;
private static void Main(string[] args)
{
int maxWorkers, maxIOCPs;
ThreadPool.GetMaxThreads(out maxWorkers, out maxIOCPs);
int minWorkers, minIOCPs;
ThreadPool.GetMinThreads(out minWorkers, out minIOCPs);
Console.WriteLine(new { maxWorkers, maxIOCPs, minWorkers, minIOCPs });
ThreadPool.SetMinThreads(100, 100);
var tasks = new List<Task>();
for (int i = 1; i <= totalCalls; i++)
tasks.Add(Task.Run(() => GoogleSearch(i)));
Task.WaitAll(tasks.ToArray());
}
private static void GoogleSearch(object searchTerm)
{
string url = @"https://www.google.com/search?q=" + searchTerm;
Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
WebRequest wr = WebRequest.Create(url);
var httpWebResponse = (HttpWebResponse)wr.GetResponse();
var reader = new StreamReader(httpWebResponse.GetResponseStream());
string responseFromServer = reader.ReadToEnd();
//Console.WriteLine(responseFromServer); // Display the content.
reader.Close();
httpWebResponse.Close();
Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
}
}
}
Асинхронный
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace Async
{
internal class Program
{
private const int totalCalls = 100;
private static void Main(string[] args)
{
int maxWorkers, maxIOCPs;
ThreadPool.GetMaxThreads(out maxWorkers, out maxIOCPs);
int minWorkers, minIOCPs;
ThreadPool.GetMinThreads(out minWorkers, out minIOCPs);
Console.WriteLine(new { maxWorkers, maxIOCPs, minWorkers, minIOCPs });
ThreadPool.SetMinThreads(100, 100);
var tasks = new List<Task>();
for (int i = 1; i <= totalCalls; i++)
tasks.Add(GoogleSearch(i));
Task.WaitAll(tasks.ToArray());
}
private static async Task GoogleSearch(object searchTerm)
{
string url = @"https://www.google.com/search?q=" + searchTerm;
Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
WebRequest wr = WebRequest.Create(url);
var httpWebResponse = (HttpWebResponse) await wr.GetResponseAsync();
var reader = new StreamReader(httpWebResponse.GetResponseStream());
string responseFromServer = await reader.ReadToEndAsync();
//Console.WriteLine(responseFromServer); // Display the content.
reader.Close();
httpWebResponse.Close();
Console.WriteLine("Total number of threads in use={0}", Process.GetCurrentProcess().Threads.Count);
}
}
}
По умолчанию для пула потоков в моей системе я вижу следующие цифры:
{ maxWorkers = 32767, maxIOCPs = 1000, minWorkers = 4, minIOCPs = 4 }
Пул потоков ленив при выращивании потоков. Новое создание потока может быть отложено до 500 мс (подробнее см. Joe Duffy "CLR thread pool injection, stuttering problems" ).
Для учета этого поведения используйте ThreadPool.SetMinThreads
. С SetMinThreads(100, 100)
я вижу ~ 111 потоков на пике для версии синхронизации и ~ 20 потоков в пике для асинхронной версии (релиз сборки, работающий без отладчика). Это довольно показательная разница, от имени асинхронной версии.