在 REST API 中使用 async/wait 和 CPU 密集型任务
Use of async/wait in REST API with CPU intensive tasks
我无法理解在 REST API 中使用 async/await 的优势。
我有这个 CPU 密集型任务:
[HttpGet("GetHeavyStuffAsync")]
public async Task<string> GetHeavyStuffAsync()
{
Guid id = Guid.NewGuid();
System.Diagnostics.Debug.WriteLine($"{id} has started");
await _expensiveOperations.DoHeavyStuffOnDifferentThread();
System.Diagnostics.Debug.WriteLine($"{id} has finished");
return "request processed";
}
DoHeavyStuffOnDifferentThread()
这样做的地方:
public async Task DoHeavyStuffOnDifferentThread()
{
var t = Task.Run(() =>
{
var limit = 4000;
var array = Enumerable.Range(0, limit).ToArray();
stoogesort(array, 0, array.Count() - 1);
});
await t;
}
我正在使用 Task.Run(() => ...
以便 CPU 沉重的东西在不同的威胁中执行而不会阻塞主要威胁,并使用 async/await 希望控制器线程不会被繁重的任务挡住了,可以继续接听请求
为了测试我编写了一个针对 GetHeavyStuffAsync()
发起 250 个请求的程序,然后我大摇大摆地向同一个 API 控制器中的不同端点发出了请求:
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
System.Diagnostics.Debug.WriteLine("GETTING FORECAST ...");
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
如您所见,最后一个端点是 Visual Studio 在您创建 API 项目时默认创建的一个示例,它是一个非常简单的函数 return 马上。
我期望发生的事情:将处理对 GetHeavyStuffAsync
的调用,在 await _expensiveOperations.DoHeavyStuffOnDifferentThread();
控制权将传递给执行繁重工作的线程,而 API 控制器将可以自由地处理其他请求,在某个时候 DoHeavyStuffOnDifferentThread
将完成并且控制器将继续执行此指令 System.Diagnostics.Debug.WriteLine($"{id} has finished");
并完成该方法,可以自由地继续处理其他请求。
实际发生的事情是 public IEnumerable<WeatherForecast> Get()
花了几分钟才 return。
那么,如果我没有使用不同的线程或 async/await,为什么我的行为与我的行为没有区别?
(注:在测试过程中CPU,我的笔记本内存还剩50%左右)
I am using Task.Run(() => ... so that the cpu heavy stuff executes in a different threat without blocking the main one
在 ASP.NET 中这样做没有任何好处。
在桌面应用程序中,UI 只能由主线程更新。因此,将 CPU 密集型任务卸载到另一个线程是有意义的,因为它释放了 UI 线程以继续响应用户输入。
然而,在 ASP.NET 中,没有一个“主线程”。每个新请求都会分配一个新线程,直到达到最大线程池计数,然后任何其他请求都必须等待。
因此,当您使用 Task.Run
时,您正在释放主请求的线程,但您正在使用另一个线程。所以对 ThreadPool 计数的净影响仍然相同。
在文章ASP.NET Core Performance Best Practices中,微软推荐:
Do not:
- Call Task.Run and immediately await it. ASP.NET Core already runs app code on normal Thread Pool threads, so calling Task.Run only results in extra unnecessary Thread Pool scheduling. Even if the scheduled code would block a thread, Task.Run does not prevent that.
异步代码仅在发出 I/O 请求(网络、文件系统等)时帮助您,因为在等待期间确实无事可做。但是对于 CPU 密集型任务,没有任何好处。
我无法理解在 REST API 中使用 async/await 的优势。 我有这个 CPU 密集型任务:
[HttpGet("GetHeavyStuffAsync")]
public async Task<string> GetHeavyStuffAsync()
{
Guid id = Guid.NewGuid();
System.Diagnostics.Debug.WriteLine($"{id} has started");
await _expensiveOperations.DoHeavyStuffOnDifferentThread();
System.Diagnostics.Debug.WriteLine($"{id} has finished");
return "request processed";
}
DoHeavyStuffOnDifferentThread()
这样做的地方:
public async Task DoHeavyStuffOnDifferentThread()
{
var t = Task.Run(() =>
{
var limit = 4000;
var array = Enumerable.Range(0, limit).ToArray();
stoogesort(array, 0, array.Count() - 1);
});
await t;
}
我正在使用 Task.Run(() => ...
以便 CPU 沉重的东西在不同的威胁中执行而不会阻塞主要威胁,并使用 async/await 希望控制器线程不会被繁重的任务挡住了,可以继续接听请求
为了测试我编写了一个针对 GetHeavyStuffAsync()
发起 250 个请求的程序,然后我大摇大摆地向同一个 API 控制器中的不同端点发出了请求:
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
System.Diagnostics.Debug.WriteLine("GETTING FORECAST ...");
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
如您所见,最后一个端点是 Visual Studio 在您创建 API 项目时默认创建的一个示例,它是一个非常简单的函数 return 马上。
我期望发生的事情:将处理对 GetHeavyStuffAsync
的调用,在 await _expensiveOperations.DoHeavyStuffOnDifferentThread();
控制权将传递给执行繁重工作的线程,而 API 控制器将可以自由地处理其他请求,在某个时候 DoHeavyStuffOnDifferentThread
将完成并且控制器将继续执行此指令 System.Diagnostics.Debug.WriteLine($"{id} has finished");
并完成该方法,可以自由地继续处理其他请求。
实际发生的事情是 public IEnumerable<WeatherForecast> Get()
花了几分钟才 return。
那么,如果我没有使用不同的线程或 async/await,为什么我的行为与我的行为没有区别?
(注:在测试过程中CPU,我的笔记本内存还剩50%左右)
I am using Task.Run(() => ... so that the cpu heavy stuff executes in a different threat without blocking the main one
在 ASP.NET 中这样做没有任何好处。
在桌面应用程序中,UI 只能由主线程更新。因此,将 CPU 密集型任务卸载到另一个线程是有意义的,因为它释放了 UI 线程以继续响应用户输入。
然而,在 ASP.NET 中,没有一个“主线程”。每个新请求都会分配一个新线程,直到达到最大线程池计数,然后任何其他请求都必须等待。
因此,当您使用 Task.Run
时,您正在释放主请求的线程,但您正在使用另一个线程。所以对 ThreadPool 计数的净影响仍然相同。
在文章ASP.NET Core Performance Best Practices中,微软推荐:
Do not:
- Call Task.Run and immediately await it. ASP.NET Core already runs app code on normal Thread Pool threads, so calling Task.Run only results in extra unnecessary Thread Pool scheduling. Even if the scheduled code would block a thread, Task.Run does not prevent that.
异步代码仅在发出 I/O 请求(网络、文件系统等)时帮助您,因为在等待期间确实无事可做。但是对于 CPU 密集型任务,没有任何好处。