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

前端 未结 2 1923
清歌不尽
清歌不尽 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<string> 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<Task> 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.

    0 讨论(0)
  • 2021-01-03 06:44

    Marking the methods async means that we can use the await keyword to basically schedule the heavy processing on another thread and free the UI thread until the processing is finished.

    That's not at all how async works. See my async intro.

    You may say that the *Async methods use some special magic (like handling external signals) that make them more efficient than their synchronous counterparts. The thing is that I don't see this beeing the case.

    In pure asynchronous code, there is no thread (as I explain on my blog). In fact, at the device driver level, all (non-trivial) I/O is asynchronous. It is the synchronous APIs (at the OS level) that are an abstraction layer over the natural, asynchronous APIs.

    Let's look at the Stream.ReadAsync for example.

    Stream is an unusual case. As a base class, it has to prevent breaking changes as much as possible. So, when they added the virtual ReadAsync method, they had to add a default implementation. This implementation has to use a non-ideal implementation (Task.Run), which is unfortunate. In an ideal world, ReadAsync would be (or call) an abstract asynchronous implementation, but that would break every existing implementation of Stream.

    For a more proper example, compare the difference between WebClient and HttpClient.

    0 讨论(0)
提交回复
热议问题