Ответ 1
Я думаю, что есть неплохие шансы, что вы не тестируете то, что, по вашему мнению, испытываете. Из того, что я могу собрать, вы пытаетесь обнаружить выпуски обратно в пул потоков, сравнивая тайминги и вызывая инъекцию потоков.
С одной стороны, настройки по умолчанию для пула потоков на .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-логическом ядре) машине я вижу вывод следующим образом:
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
Что ясно показывает, что асинхронный метод возвращает поток в пул потоков.