What is the purpose of the *Async methods in .Net Framework given the ability to run any method asynchronously using Task.Run?

前端 未结 2 1924
清歌不尽
清歌不尽 2021-01-03 06:30

Short question:

Why did .Net Framework add a lot of *Async versions of method instead of developers just using Task.Run to run synchron

2条回答
  •  孤城傲影
    2021-01-03 06:43

    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.

提交回复
热议问题