为什么我们需要异步任务,默认 web API 请求将 运行 通过仅创建或重用现有线程
Why we need Async task any way default web API request will be running by creating or reusing existing thread only
为什么我们在 Web Api c# 中需要异步任务,无论如何默认的 Web API 请求将 运行 通过仅创建或重用现有线程。所以线程已经被使用了?
不要混淆异步和并行。
异步意味着当前线程在您等待对某些 I/O 操作的响应时被释放。 (本地存储、网络请求等)
并行是指运行同时进行两套或多套代码。这是多线程。
异步代码与多线程无关。实际上恰恰相反:异步代码的部分好处是不需要需要更多线程。
例如,考虑一个从数据库读取数据的 Web API 调用。 1000个请求同时进来会怎样?
如果代码是同步写的,那么每个请求都需要一个单独的线程。但是 ASP.NET 有最大线程数。这样就达到了max,剩下的请求就得等到一些最开始的请求完成后才能连start.
如果代码是异步编写的,那么一旦数据库请求发出,线程就会被释放,等待数据库的响应。在那段等待时间内,ASP.NET 可以使用该线程开始处理新请求。
结果是您需要更少的线程来完成相同的工作量。这也意味着您可以使用相同数量的资源完成 更多 工作。
Microsoft 对此有一个写得很好的系列文章,值得一读:Asynchronous programming with async and await。那篇文章有一个关于做早餐的类比,有助于解释异步编程的真正含义。
谢谢,加布里埃尔,你是对的
找到更明确的答案
来自 link :
现在让我们以 "asynchronous" 模式打开文件:
public async Task<IActionResult> GetSomeFileReallyAsync(RequestParameters p) {
string filePath = Preprocess(p);
byte[] data;
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous)) {
data = new byte[fs.Length];
await fs.ReadAsync(data, 0, data.Length);
}
return PostProcess(data);
}
我们现在需要多少线程?理论上,现在 1 个线程就足够了。当您以 "asynchronous" 模式打开文件时 - 读取和写入将利用(在 windows 上)windows 重叠 IO。
简单来说,它是这样工作的:有一个类似队列的对象(IO 完成端口),其中 OS 可以 post 关于某些 IO 操作完成的通知。 .NET 线程池注册了一个这样的 IO 完成端口。每个.NET应用程序只有一个线程池,所以有一个IO完成端口。
当文件以 "asynchronous" 模式打开时 - 它将其文件句柄绑定到此 IO 完成端口。现在,当您执行 ReadAsync 时,在执行实际读取时 - 没有专用(用于此特定操作)线程被阻止等待读取完成。当 OS 通知 .NET 完成端口此文件句柄的 IO 已完成时 - .NET 线程池在线程池线程上执行延续。
现在让我们看看在我们的场景中如何处理 100 个间隔为 1ms 的请求:
请求1进入,我们从池中抓取线程执行1ms的预处理步骤。然后线程执行异步读取。它不需要阻塞等待完成,所以它 returns 到池中。
请求 2 进入。池中已经有一个线程刚刚完成请求 1 的预处理。我们不需要额外的线程 - 我们可以再次使用那个线程。
所有 100 个请求都是如此。
在处理完 100 个请求的预处理后,距离第一个 IO 完成还有 200ms,在此期间我们的 1 线程可以做更多有用的工作。
IO 完成事件开始到达 - 但我们的 post- 处理步骤也很短(1 毫秒)。再次只有一个线程可以处理所有这些。
这当然是一个理想化的场景,但它显示了如何不是 "async await" 但具体的异步 IO 可以帮助您 "save threads"。
如果我们的 post 处理步骤并不短,但我们决定在其中进行繁重的 CPU 绑定工作怎么办?那么,这将导致线程池饥饿。线程池将立即创建新线程,直到达到可配置的 "low watermark"(您可以通过 ThreadPool.GetMinThreads() 获取并通过 ThreadPool.SetMinThreads() 更改)。达到该线程数后 - 线程池将尝试等待其中一个繁忙的线程空闲。它当然不会永远等待,通常它会等待 0.5-1 秒,如果没有线程空闲 - 它会创建一个新线程。不过,在重负载情况下,这种延迟可能会大大降低您的 Web 应用程序的速度。所以不要违反线程池假设 - 不要 运行 长时间 CPU 绑定线程池线程。
为什么我们在 Web Api c# 中需要异步任务,无论如何默认的 Web API 请求将 运行 通过仅创建或重用现有线程。所以线程已经被使用了?
不要混淆异步和并行。
异步意味着当前线程在您等待对某些 I/O 操作的响应时被释放。 (本地存储、网络请求等)
并行是指运行同时进行两套或多套代码。这是多线程。
异步代码与多线程无关。实际上恰恰相反:异步代码的部分好处是不需要需要更多线程。
例如,考虑一个从数据库读取数据的 Web API 调用。 1000个请求同时进来会怎样?
如果代码是同步写的,那么每个请求都需要一个单独的线程。但是 ASP.NET 有最大线程数。这样就达到了max,剩下的请求就得等到一些最开始的请求完成后才能连start.
如果代码是异步编写的,那么一旦数据库请求发出,线程就会被释放,等待数据库的响应。在那段等待时间内,ASP.NET 可以使用该线程开始处理新请求。
结果是您需要更少的线程来完成相同的工作量。这也意味着您可以使用相同数量的资源完成 更多 工作。
Microsoft 对此有一个写得很好的系列文章,值得一读:Asynchronous programming with async and await。那篇文章有一个关于做早餐的类比,有助于解释异步编程的真正含义。
谢谢,加布里埃尔,你是对的
找到更明确的答案 来自 link :
现在让我们以 "asynchronous" 模式打开文件:
public async Task<IActionResult> GetSomeFileReallyAsync(RequestParameters p) {
string filePath = Preprocess(p);
byte[] data;
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous)) {
data = new byte[fs.Length];
await fs.ReadAsync(data, 0, data.Length);
}
return PostProcess(data);
}
我们现在需要多少线程?理论上,现在 1 个线程就足够了。当您以 "asynchronous" 模式打开文件时 - 读取和写入将利用(在 windows 上)windows 重叠 IO。
简单来说,它是这样工作的:有一个类似队列的对象(IO 完成端口),其中 OS 可以 post 关于某些 IO 操作完成的通知。 .NET 线程池注册了一个这样的 IO 完成端口。每个.NET应用程序只有一个线程池,所以有一个IO完成端口。
当文件以 "asynchronous" 模式打开时 - 它将其文件句柄绑定到此 IO 完成端口。现在,当您执行 ReadAsync 时,在执行实际读取时 - 没有专用(用于此特定操作)线程被阻止等待读取完成。当 OS 通知 .NET 完成端口此文件句柄的 IO 已完成时 - .NET 线程池在线程池线程上执行延续。
现在让我们看看在我们的场景中如何处理 100 个间隔为 1ms 的请求:
请求1进入,我们从池中抓取线程执行1ms的预处理步骤。然后线程执行异步读取。它不需要阻塞等待完成,所以它 returns 到池中。
请求 2 进入。池中已经有一个线程刚刚完成请求 1 的预处理。我们不需要额外的线程 - 我们可以再次使用那个线程。
所有 100 个请求都是如此。
在处理完 100 个请求的预处理后,距离第一个 IO 完成还有 200ms,在此期间我们的 1 线程可以做更多有用的工作。
IO 完成事件开始到达 - 但我们的 post- 处理步骤也很短(1 毫秒)。再次只有一个线程可以处理所有这些。
这当然是一个理想化的场景,但它显示了如何不是 "async await" 但具体的异步 IO 可以帮助您 "save threads"。
如果我们的 post 处理步骤并不短,但我们决定在其中进行繁重的 CPU 绑定工作怎么办?那么,这将导致线程池饥饿。线程池将立即创建新线程,直到达到可配置的 "low watermark"(您可以通过 ThreadPool.GetMinThreads() 获取并通过 ThreadPool.SetMinThreads() 更改)。达到该线程数后 - 线程池将尝试等待其中一个繁忙的线程空闲。它当然不会永远等待,通常它会等待 0.5-1 秒,如果没有线程空闲 - 它会创建一个新线程。不过,在重负载情况下,这种延迟可能会大大降低您的 Web 应用程序的速度。所以不要违反线程池假设 - 不要 运行 长时间 CPU 绑定线程池线程。