在 Web API 控制器(. net core)中使用异步/等待或任务

我有一个.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);
}

这个工作很好,但是我想知道是否使用任务是最好的解决方案在这里?使用异步/等待是一个更好的主意和更被接受的方式吗?

例如,是否应该将控制器标记为异步的,并在对服务方法的每次调用中加入等待?

128131 次浏览
[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);
}
  1. 当等待任务时,操作线程当前被阻塞。使用 TaskWhenAll返回可等待的 Task 对象。因此,使用异步方法,您可以等待任务,而不是阻塞线程。
  2. 可以使用 Task<T>返回所需类型的结果,而不是创建局部变量并在任务中分配它们。
  3. 使用 Task<TResult>.Run方法代替创建和运行任务
  4. 我建议对操作名称使用约定-如果操作接受 GET 请求,它的名称应该以 Get开头
  5. 接下来,您应该检查任务是否成功完成。它是通过检查任务状态来完成的。在我的示例中,如果某些任务没有成功完成,则使用 null值来返回对象属性。您可以使用另一种方法-例如,如果某些任务失败,则返回错误。

据我所知,您希望并行执行此操作,因此我认为您的代码没有任何问题。正如加布里埃尔所说,你可以等待任务完成。

[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);
}

您还可以使用任务的结果来保存一些代码行,并使代码总体“更好”(参见注释) :

[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);
}

这个工作很好,但是我想知道是否使用任务是最好的解决方案在这里?使用异步/等待是一个更好的主意和更被接受的方式吗?

是的,当然。在 ASP.NET 上执行并行处理会为每个请求消耗多个线程,这会严重影响您的可伸缩性。异步处理在 I/O 方面要优越得多。

要使用 async,首先从服务内部的最低级别调用开始。它可能在某个时候执行 HTTP 调用; 将其更改为使用异步 HTTP 调用(例如,HttpClient)。然后让 async从那里自然生长。

最终,您将得到异步 getdata1Asyncgetdata2Asyncgetdata3Async方法,这些方法可以并发使用:

[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使用 线程而不是 线程。