ChatGPT解决这个技术问题 Extra ChatGPT

在 web api 控制器(.net 核心)中使用 async/await 或任务

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

S
Stephen Cleary

这很好用,但是我想知道使用任务是否是最好的解决方案?使用 async/await 会是一个更好的主意和更被接受的方式吗?

是的,一点没错。在 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 使用 个线程而不是 四个


为 returnObject 中的每个任务执行 await Task.WhenAll() 并等待不是多余的吗?不知道为什么仅仅做一个或另一个是不够的。你能解释一下吗? :)
如果返回类型不同,您必须await每个任务。即使在这种情况下,我也更喜欢 Task.WhenAll,因为它使代码更清晰。它也更有效率(只在上下文中恢复一次而不是 3 次),但我的主要原因是代码清晰。
@StephenCleary 如果我的 API 需要使用 WaitForExit 调用 System.Diagnostics.Process,如何使用零线程?我计划实施 stackoverflow.com/a/10789196/241004 作为解决方法。你有更好的主意吗?
@JawadAlShaikh:不,这是你能做的最好的事情。 Process 只是没有非常适合异步的 API。 :/
@StephenPatten:是的。
S
Sergey Berezovskiy
[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 对象。因此,使用异步方法,您可以等待任务而不是阻塞线程。您可以使用 Task 返回所需类型的结果,而不是创建局部变量并在任务中分配它们。不要创建和运行任务,而是使用 Task.Run 方法,我建议对操作名称使用约定 - 如果操作接受 GET 请求,它的名称应该以 Get Next 开头,您应该检查任务是否成功完成。这是通过检查任务状态来完成的。在我的示例中,如果某些任务未成功完成,我将使用空值作为返回对象属性。您可以使用另一种方法 - 例如,如果某些任务失败,则返回错误。


我认为您需要解释 RanToCompletion 的检查。你期待提前取消吗?如果任何调用出错,await Task.WhenAll 将引发异常
你确定你可以重命名 WebApi 中的 get 方法吗?在 MVC 中,你不能不改变行为。
@Sefe 是的,你是对的。没注意到mvc core,一会儿会还原一些web-api相关的改动
T
Thomas D.

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

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

第一个片段包含几个问题 - 使用一次性对象初始化变量,然后捕获这些变量。
我同意这不是最优的,这就是为什么我用我的第二个代码片段提供了一个“更好”的解决方案。无论如何,我不认为第一次剪断有真正的问题。编译器会将一次性局部变量转换为私有字段,这应该可以正常工作。 (再一次,我知道这并不理想。)
由于异步使用,至少您应该使用 Task 作为方法类型
@CanPERK:你说得对,我已经添加了建议。