的*异步方法.NET Framework中给出使用Task.Run异步运行的任何方法的能力,目的是什么?目的、方法、能力、NET

2023-09-03 00:15:52 作者:你是我无法触及的光

短的问题:

为什么.Net框架增添了许多*异步版本的方法,而不要只使用开发 Task.Run 来运行同步方法异步?

详细问题:

在我的理解asynchronisity的概念。 我知道任务 在我了解的异步/等待的关键字。 我知道* .NET Framework中的异步方法做的。

我不明白的是,在库中的*异步方法的目的。

假设你有两行code:

  F1();
F2();
 

使用对于数据/控制流只有两种情况:

F2 需要 F1之后被执行完成。 F2 不需要等待 F1 来完成。

我看不出有任何其他案件。我看不出有任何一般需要知道执行某些功能(除了UI)的具体线索。的code在线程的基本运行模式是同步的。的平行度,需要多个线程。该asynchronisity是基于并行性和code重新排序。但基础仍然同步

的差异并不重要,当 F1 的工作量小。但是,当需要花费大量的时间来完成,我们可能需要看情况,如果 F2 不需要等待 F1 来完成,我们可以并行使用 F2

运行 F1

很久以前,我们这样做,使用线程/线程池。现在我们有任务

如果我们要运行 F1 F2 同时,我们可以这样写:

  VAR任务1 = Task.Run(F1);
F2();
 
Microsoft .NET Framework Microsoft .NET FrameworkV2.0SP2下载

任务是凉爽,我们可以使用等待的地方,我们最终需要的任务完成。

到目前为止,我没有看到任何需要做一个 F1Async()方法。

现在,让我们来看看一些特殊情况。 唯一真正的特殊情况下,我看到的是UI。在UI线程是特殊的上前,使得UI冻结这是坏的。 在我看来,微软通知:我们纪念UI事件处理程序异步。打标方法异步意味着我们可以使用计谋关键字基本上安排繁重处理在另一个线程和自由的直到处理结束UI线程

我不明白再来就是为什么我们需要的任何*异步方法,以便能够在等着他们。我们总是可以只写等待Task.Run(F1); 。为什么我们需要 F1Async

您可能会说,*异步方法使用一些特殊的魔法(比如处理外部信号),使他们比同步同行更有效率。问题是,我没有看到这个beeing的情况。

让我们来看看 Stream.ReadAsync 为例。如果你看一下源$ C ​​$ C, ReadAsync 只是浪费几百行花俏code的创建只是调用同步读方法。我们为什么需要它呢?为什么不使用 Task.Run Stream.Read

这是为什么我不明白,需要通过创建同步方法琐碎*异步复制到膨胀的图书馆。 MS可能甚至还添加了语法糖,这样我们就可以写等待异步Stream.Read 而不是等待Stream.ReadAsync Task.Run(Stream.Read)

现在你可能会问:为什么不把*异步方法唯一的,删除同步的方法?。正如我刚才所说,基地code执行模式是同步的。这很容易异步运行同步的方法,而不是其他的方式。

那么,什么是的*异步方法.NET Framework中给出使用Task.Run异步运行的任何方法的能力,目的是什么?

P.S。如果非冻结用户界面是如此重要,为什么不只是在默认情况下和prevent运行处理异步冻结的任何机会呢?

无主题的说法:

人回答这个问题似乎暗示着*异步方法的好处是,他们是有效率的,因为他们不创造新主题。问题是,我没有看到这样的行为。并行异步任务的行为就像我想 - 一个线程创建(或从线程池中获取)为每个并行任务(不是所有的任务并行执行,虽然)

下面是我的测试code:

 使用系统;
使用System.Collections.Generic;
使用System.Diagnostics程序;
使用System.Linq的;
使用System.Net.Http;
使用的System.Threading;
使用System.Threading.Tasks;

命名空间ConsoleApplication32167 {
    类节目{
        静态异步任务TestAsync(){
            VAR的HttpClient =新的HttpClient(){超时= TimeSpan.FromMinutes(20)};

            变种任务= Enumerable.Range(1,100)。选择((ⅰ)=>
                httpClient.GetStringAsync(HTTP://本地主机/ SlowWebsite /));

            Console.WriteLine(完成之前的话题:+ Process.GetCurrentProcess()Threads.Count);

            等待Task.WhenAll(任务);

            Console.WriteLine(结束后,主题:+ Process.GetCurrentProcess()Threads.Count);
        }

        静态无效的主要(字串[] args){
            Console.WriteLine(线程启动时:+ Process.GetCurrentProcess()Threads.Count);

            VAR定时器=新的秒表();
            timer.Start();

            变种testTask = TestAsync();

            VAR distinctThreadIds =新的HashSet<诠释>();
            而(!testTask.IsCompleted){
                VAR threadIds = Process.GetCurrentProcess()Threads.OfType< ProcessThread>()选择(线程=> thread.Id).ToList();
                distinctThreadIds.UnionWith(threadIds);
                Console.WriteLine(当前线程数:{0};累计线程数:{1},threadIds.Count,distinctThreadIds.Count);
                Thread.sleep代码(250);
            }

            testTask.Wait();

            Console.WriteLine(timer.Elapsed);
            到Console.ReadLine();
        }
    }
}
 

这code试图运行100 HttpClient.GetStringAsync 任务提出请求,以一个网站,需要1分钟内做出回应。同时它计算活动线程的数目和不同的过程中产生的累计次数。正如我predicted,这一计划创造了许多新的线程。输出看起来是这样的:

 当前线程数:4;累计线程数:4。
....
当前线程数:25;累计线程数:25。
....
当前线程数:7;累计线程数:63。
当前线程数:9;累计线程数:65。
00:10:01.9981006
 

这意味着:

在异步任务执行过程中创建在61个新主题。 在新的活动的线程数的峰值是21。 的执行需要10倍以上的时间(10分钟,而不是1)。的这是由本地IIS的限制造成的。的 解决方案   

标记的方法异步意味着我们可以使用的await关键字基本上安排繁重处理在另一个线程并释放UI线程,直到处理完毕。

这是不是在所有如何异步的作品。见我 异步介绍。

  

您可能会说,*异步方法使用一些特殊的魔法(比如处理外部信号),使他们比同步同行更有效率。问题是,我没有看到这个beeing的情况。

在纯异步code,没有线程(因为我在我的博客解释)。事实上,在设备驱动程序级别,所有的(不平凡)I / O是异步的。这是同步的API(在操作系统级别)是一个抽象层,过度自然,异步的API。

  

让我们来看看Stream.ReadAsync为例。

是一个不寻常的情况。作为基类,它具有prevent破尽可能的变化。所以,当他们增加了虚拟 ReadAsync 的方法,他们不得不增加一个默认的实现。这实现了用一个非理想的实现( Task.Run ),这是不幸的。在一个理想的世界, ReadAsync 将(或称)的抽象异步执行,但会破坏流。

有关更合适的例子,比较 Web客户端的HttpClient

之间的区别

Short question:

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

Detailed question:

I understand the concept of asynchronisity. I know about Tasks I know about the async/await keywords. I know what *Async methods in .Net Framework do.

What I don't understand is the purpose of the *Async methods in the library.

Suppose that you have two lines of code:

F1();
F2();

With respect to the data/control flow there are only two cases:

F2 need to be executed after F1 finishes. F2 does not need to wait for F1 to finish.

I don't see any other cases. I don't see any general need to know the concrete thread that executes some function (apart from UI). The base execution mode of code in a thread is synchronous. The parallelism requires multiple threads. The asynchronisity is based on parallelism and code reordering. But the base is still synchronous.

The difference does not matter when the F1's workload is small. But when A takes a lot of time to finish, we may need to look at the situation and, if F2 does not need to wait for F1 to finish, we can run F1 in parallel with F2.

Long time ago we did that using threads/thread pools. Now we have Tasks.

If we want to run F1 and F2 in parallel, we can write:

var task1 = Task.Run(F1);
F2();

tasks are cool and we can use await in places where we finally need the task to be finished.

So far, I don't see any need to make an F1Async() method.

Now, let's look at some special cases. The only real special case I see is UI. The UI thread is special and stalling it makes the UI freeze which is bad. As I see it, Microsoft advices us to mark the UI event handlers async. 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.

What I don't get again is why do we need any *Async methods to be able to await them. We can always just write await Task.Run(F1);. Why would we need F1Async?

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.

Let's look at the Stream.ReadAsync for example. If you look at the source code, ReadAsync just wastes several hundred lines of bells and whistles code to create a task that just calls the synchronous Read method. Why do we need it then? Why not just use Task.Run with Stream.Read?

This is why I don't understand the need to bloat the libraries by creating the trivial *Async copies of synchronous methods. MS could have even added the syntactic sugar, so that we could write await async Stream.Read instead of await Stream.ReadAsync or Task.Run(Stream.Read).

Now you may ask "Why not make the *Async methods the only ones and remove the synchronous methods?". As I've said earlier, the base code execution mode is synchronous. It's easy to run synchronous method asynchronously, but not the other way.

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

P.S. If the non-freezing the UI is so important, why not just run the handlers async by default and prevent any chance of freezing?

The "no threads" argument:

People answering this question seem to imply that the advantage of *Async methods is that they are efficient because they don't create new threads. The problem is that I don't see such behavior. The parallel asynchronous tasks behave just like I thought - a thread is created (or taken from the thread pool) for each parallel task (not all tasks are executed in parallel though).

Here is my test code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication32167 {
    class Program {
        static async Task TestAsync() {
            var httpClient = new HttpClient() { Timeout = TimeSpan.FromMinutes(20) };

            var tasks = Enumerable.Range(1, 100).Select((i) =>
                httpClient.GetStringAsync("http://localhost/SlowWebsite/"));

            Console.WriteLine("Threads before completion: " + Process.GetCurrentProcess().Threads.Count);

            await Task.WhenAll(tasks);

            Console.WriteLine("Threads after completion: " + Process.GetCurrentProcess().Threads.Count);
        }

        static void Main(string[] args) {
            Console.WriteLine("Threads at start: " + Process.GetCurrentProcess().Threads.Count);

            var timer = new Stopwatch();
            timer.Start();

            var testTask = TestAsync();

            var distinctThreadIds = new HashSet<int>();
            while (!testTask.IsCompleted) {
                var threadIds = Process.GetCurrentProcess().Threads.OfType<ProcessThread>().Select(thread => thread.Id).ToList();
                distinctThreadIds.UnionWith(threadIds);
                Console.WriteLine("Current thread count: {0}; Cumulative thread count: {1}.", threadIds.Count, distinctThreadIds.Count);
                Thread.Sleep(250);
            }

            testTask.Wait();

            Console.WriteLine(timer.Elapsed);
            Console.ReadLine();
        }
    }
}

This code tries to run 100 HttpClient.GetStringAsync tasks making requests to a website that takes 1 minute to respond. At the same time it counts the number of active threads and the cumulative number of different created by the process. As I've predicted, this program creates many new threads. The output looks like this:

Current thread count: 4; Cumulative thread count: 4.
....
Current thread count: 25; Cumulative thread count: 25.
....
Current thread count: 7; Cumulative thread count: 63.
Current thread count: 9; Cumulative thread count: 65.
00:10:01.9981006

This means that:

61 new threads are created during the course of the async task execution. The peak number of new active threads is 21. The execution takes 10x more time (10 minutes instead of 1).This was caused by the local IIS limits.

解决方案

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.