Short question:
Why did .Net Framework add a lot of *Async versions of method instead of developers just using Task.Run
to run synchron
Let's do the realistic test: naturally asynchronous WebRequest.GetResponseAsync vs unnaturally synchronous WebRequest.GetResponse.
First, we extend the standard limits of the ThreadPool
:
ThreadPool.SetMaxThreads(MAX_REQS * 2, MAX_REQS * 2);
ThreadPool.SetMinThreads(MAX_REQS, MAX_REQS);
Note I request the same number of workerThreads
and completionPortThreads
. Then we'll perform MAX_REQS
= 200 parallel requests to bing.com, using each API.
The code (a standalone console app):
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using System.Net;
namespace Console_21690385
{
class Program
{
const int MAX_REQS = 200;
// implement GetStringAsync
static async Task GetStringAsync(string url)
{
using (var response = await WebRequest.Create(url).GetResponseAsync())
using (var stream = response.GetResponseStream())
using (var reader = new System.IO.StreamReader(stream))
{
return await reader.ReadToEndAsync();
}
}
// test using GetStringAsync
static async Task TestWithGetStringAsync()
{
var tasks = Enumerable.Range(1, MAX_REQS).Select((i) =>
GetStringAsync("http://www.bing.com/search?q=item1=" + i));
Console.WriteLine("Threads before completion: " + Process.GetCurrentProcess().Threads.Count);
await Task.WhenAll(tasks);
Console.WriteLine("Threads after completion: " + Process.GetCurrentProcess().Threads.Count);
}
// implement GetStringSync
static string GetStringSync(string url)
{
using (var response = WebRequest.Create(url).GetResponse())
using (var stream = response.GetResponseStream())
using (var reader = new System.IO.StreamReader(stream))
{
return reader.ReadToEnd();
}
}
// test using GetStringSync
static async Task TestWithGetStringSync()
{
var tasks = Enumerable.Range(1, MAX_REQS).Select((i) =>
Task.Factory.StartNew(
() => GetStringSync("http://www.bing.com/search?q=item1=" + i),
CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default));
Console.WriteLine("Threads before completion: " + Process.GetCurrentProcess().Threads.Count);
await Task.WhenAll(tasks);
Console.WriteLine("Threads after completion: " + Process.GetCurrentProcess().Threads.Count);
}
// run either of the tests
static void RunTest(Func runTest)
{
Console.WriteLine("Threads at start: " + Process.GetCurrentProcess().Threads.Count);
var stopWatch = new Stopwatch();
stopWatch.Start();
var testTask = runTest();
while (!testTask.IsCompleted)
{
Console.WriteLine("Currently threads: " + Process.GetCurrentProcess().Threads.Count);
Thread.Sleep(1000);
}
Console.WriteLine("Threads at end: " + Process.GetCurrentProcess().Threads.Count + ", time: " + stopWatch.Elapsed);
testTask.Wait();
}
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(MAX_REQS * 2, MAX_REQS * 2);
ThreadPool.SetMinThreads(MAX_REQS, MAX_REQS);
Console.WriteLine("Testing using GetStringAsync");
RunTest(TestWithGetStringAsync);
Console.ReadLine();
Console.WriteLine("Testing using GetStringSync");
RunTest(TestWithGetStringSync);
Console.ReadLine();
}
}
}
Output:
Testing using GetStringAsync
Threads at start: 3
Threads before completion: 3
Currently threads: 25
Currently threads: 84
Currently threads: 83
Currently threads: 83
Currently threads: 83
Currently threads: 83
Currently threads: 83
Currently threads: 84
Currently threads: 83
Currently threads: 83
Currently threads: 84
Currently threads: 84
Currently threads: 84
Currently threads: 83
Currently threads: 83
Currently threads: 84
Currently threads: 83
Currently threads: 82
Currently threads: 82
Currently threads: 82
Currently threads: 83
Currently threads: 25
Currently threads: 25
Currently threads: 26
Currently threads: 25
Currently threads: 25
Currently threads: 25
Currently threads: 23
Currently threads: 23
Currently threads: 24
Currently threads: 20
Currently threads: 20
Currently threads: 19
Currently threads: 19
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 19
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 18
Currently threads: 18
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 18
Currently threads: 17
Threads after completion: 17
Threads at end: 17, time: 00:00:51.2605879
Testing using GetStringSync
Threads at start: 15
Threads before completion: 15
Currently threads: 55
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 212
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 205
Currently threads: 201
Currently threads: 196
Currently threads: 190
Currently threads: 186
Currently threads: 182
Threads after completion: 178
Threads at end: 173, time: 00:00:47.2603652
The result:
Both tests takes about 50 seconds to complete, but GetStringAsync
peaks at 83 threads, while GetStringSync
does at 213. The higher the MAX_REQS
figure goes, the more threads are wasted by the blocking WebRequest.GetResponse
API.
@Ark-kun, I hope you see the point now.