在 web api 控制器(.net 核心)中使用 async/await 或任务
Posted
技术标签:
【中文标题】在 web api 控制器(.net 核心)中使用 async/await 或任务【英文标题】:Using async/await or task in web api controller (.net core) 【发布时间】:2017-06-16 15:00:28 【问题描述】:我有一个 .net 核心 API,它有一个控制器,用于构建一个聚合对象以返回。 它创建的对象由来自对服务类的 3 个方法调用的数据组成。这些都是相互独立的,可以相互隔离运行。 目前我正在使用任务来提高这个控制器的性能。当前版本看起来像这样......
[HttpGet]
public IActionResult myControllerAction()
var data1 = new sometype1();
var data2 = new sometype2();
var data3 = new List<sometype3>();
var t1 = new Task(() => data1 = service.getdata1(); );
t1.Start();
var t2 = new Task(() => data2 = service.getdata2(); );
t2.Start();
var t3 = new Task(() => data3 = service.getdata2(); );
t3.Start();
Task.WaitAll(t1, t2, t3);
var data = new returnObject
d1 = data1,
d2 = data2,
d2 = data3
;
return Ok(data);
这很好,但是我想知道使用任务是否是最好的解决方案?使用 async/await 会是更好的主意和更被接受的方式吗?
例如,控制器是否应该被标记为异步并在每次调用服务方法时放置等待?
【问题讨论】:
service
不为getdata
方法提供异步替代方案吗?此外,通常您应该更喜欢Task.Run()
,而不是创建任务然后手动启动它们。
async/await 在这个意义上对并行性没有帮助。实际上,它会序列化您的 3 个服务请求。但是,当您执行 Task.WaitAll
时,它会有所帮助,因为您正在用该行停放一个线程。
根本没有理由创建冷任务然后对它们调用Start
。它们不是线程。只需使用Task.Run
。然后使用await Task.WhenAll
【参考方案1】:
据我了解,您希望它并行执行,所以我认为您的代码没有任何问题。正如 Gabriel 所说,您可以等待任务完成。
[HttpGet]
public async Task<IActionResult> myControllerAction()
var data1 = new sometype1();
var data2 = new sometype2();
var data3 = new List<sometype3>();
var t1 = Task.Run(() => data1 = service.getdata1(); );
var t2 = Task.Run(() => data2 = service.getdata2(); );
var t3 = Task.Run(() => data3 = service.getdata3(); );
await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here
var data = new returnObject
d1 = data1,
d2 = data2,
d2 = data3
;
return Ok(data);
您还可以使用任务的结果来保存一些代码行并使代码整体“更好”(参见 cmets):
[HttpGet]
public async Task<IActionResult> myControllerAction()
var t1 = Task.Run(() => service.getdata1() );
var t2 = Task.Run(() => service.getdata2() );
var t3 = Task.Run(() => service.getdata3() );
await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here
var data = new returnObject
d1 = t1.Result,
d2 = t2.Result,
d2 = t3.Result
;
return Ok(data);
【讨论】:
第一个 sn-p 包含几个问题 - 用一次性对象初始化变量,然后捕获这些变量。 我同意这不是最优的,这就是为什么我用我的第二个代码 sn-p 提供了一个“更好”的解决方案。无论如何,我不认为第一次剪断有真正的问题。编译器会将一次性局部变量转换为私有字段,这应该可以正常工作。 (再一次,我知道这并不理想。) 至少你应该使用 Task 作为方法类型,因为异步使用 @CanPERK:你说得对,我已经添加了建议。【参考方案2】:这很好用,但是我想知道使用任务是否是最好的解决方案?使用 async/await 会是更好的主意和更被接受的方式吗?
是的,当然。在 ASP.NET 上进行并行处理会消耗每个请求的多个线程,这会严重影响您的可伸缩性。对于 I/O,异步处理要好得多。
要使用async
,首先从您的最低级别调用开始,在您的服务中的某处。它可能会在某个时候进行 HTTP 调用;将其更改为使用异步 HTTP 调用(例如,HttpClient
)。然后让async
从那里自然生长。
最终,您将得到异步的 getdata1Async
、getdata2Async
和 getdata3Async
方法,它们可以同时使用:
[HttpGet]
public async Task<IActionResult> myControllerAction()
var t1 = service.getdata1Async();
var t2 = service.getdata2Async();
var t3 = service.getdata3Async();
await Task.WhenAll(t1, t2, t3);
var data = new returnObject
d1 = await t1,
d2 = await t2,
d3 = await t3
;
return Ok(data);
使用这种方法,当三个服务调用正在进行时,myControllerAction
使用 零 个线程而不是 四个。
【讨论】:
对returnObject中的每个任务都做await Task.WhenAll()和await不是多余的吗?不知道为什么仅仅做一个或另一个是不够的。你能解释一下吗? :) 如果返回类型不同,你必须await
每个任务。即使在这种情况下,我也更喜欢Task.WhenAll
,因为它使代码更清晰。它也更有效率(只在上下文中恢复一次而不是 3 次),但我的主要原因是代码清晰。
@StephenCleary 如果我的 API 需要使用 WaitForExit 调用 System.Diagnostics.Process,如何使用零线程?我计划实施***.com/a/10789196/241004 作为解决方法。你有更好的主意吗?
@JawadAlShaikh:不,这是你能做的最好的事情。 Process
只是没有对异步非常友好的 API。 ://
@StephenPatten:是的。【参考方案3】:
[HttpGet]
public async Task<IActionResult> GetAsync()
var t1 = Task.Run(() => service.getdata1());
var t2 = Task.Run(() => service.getdata2());
var t3 = Task.Run(() => service.getdata3());
await Task.WhenAll(t1, t2, t3);
var data = new returnObject
d1 = t1.Status == TaskStatus.RanToCompletion ? t1.Result : null,
d2 = t2.Status == TaskStatus.RanToCompletion ? t2.Result : null,
d3 = t3.Status == TaskStatus.RanToCompletion ? t3.Result : null
;
return Ok(data);
-
当您等待任务时,您的操作线程当前被阻塞。使用
TaskWhenAll
返回可等待的任务对象。因此,使用异步方法,您可以等待任务而不是阻塞线程。
您可以使用Task<T>
返回所需类型的结果,而不是创建局部变量并在任务中分配它们。
不用创建和运行任务,而是使用Task<TResult>.Run
方法
我建议对动作名称使用约定 - 如果动作接受 GET 请求,它的名称应该以 Get
开头
接下来,您应该检查任务是否成功完成。这是通过检查任务状态来完成的。在我的示例中,如果某些任务未成功完成,我使用 null
值作为返回对象属性。您可以使用另一种方法 - 例如如果某些任务失败,则返回错误。
【讨论】:
我想你需要解释一下RanToCompletion
的检查。你期待提前取消吗?如果任何调用出错,await Task.WhenAll
将引发异常
您确定可以在 WebApi 中重命名 get 方法吗?在 MVC 中,你不能不改变行为。
@Sefe 是的,你是对的。没注意是mvc core,一会儿会还原一些web-api相关的改动以上是关于在 web api 控制器(.net 核心)中使用 async/await 或任务的主要内容,如果未能解决你的问题,请参考以下文章
模型绑定不适用于 asp.net 核心 Web api 控制器操作方法中的 Stream 类型参数。(即使使用自定义流输入格式化程序)
使用 .net 核心 Web API 对 .net 控制台应用程序进行身份验证
.net 核心 Web API - 415 不支持的媒体类型
操作过滤器在 asp.net 核心 Web API 中不起作用