对于相互交织的语句,async/await是否比Task更有优势?

本文关键字:Task 是否 await 于相互 语句 async | 更新日期: 2023-09-27 17:54:10

我有一个方法(通过AJAX请求调用),它在序列的末尾运行。在这种方法中,我保存到数据库,发送电子邮件,在其他api/数据库中查找一堆信息,将事物关联在一起,等等。我只是将原始方法重构为第二个版本,并使用Task s使其异步化,从而减少了最多2秒的运行时间。我使用Task s主要是因为它看起来更容易(我在异步/等待方面还没有经验),一些任务依赖于其他任务(如任务C、D和E都依赖于B的结果,B本身取决于A)。基本上所有的任务都是同时开始的(处理只是压缩到电子邮件任务上的Wait()调用,这以某种方式或另一种方式需要所有其他人完成。我一般都是这样做的,除了8个任务:

public thing() {
    var FooTask<x> = Task<x>.Factory.StartNew(() => {
        // ...
        return x;
    });
    var BarTask<y> = Task<y>.Factory.StartNew(() => {
      // ...
      var y = FooTask.Result;
      // ...
      return y + n;
    }
    var BazTask<z> = Task<z>.Factory.StartNew(() => {
      var y = FooTask.Result;
      return y - n;
    }
    var BagTask<z> = Task<z>.Factory.StartNew(() => {
      var g = BarTask.Result;
      var h = BazTask.Result;
      return 1;
    }
    // Lots of try/catch aggregate shenanigans.
    BagTask.Wait();
    return "yay";
}

哦,我还需要回滚以前的东西,如果有什么东西坏了,像删除数据库行,如果电子邮件发送失败,所以有几个级别的尝试/捕获在那里。无论如何,所有这些都有效(令人惊讶的是,第一次尝试就成功了)。我的问题是这种方法是否会受益于被重写为使用async/await而不是Task s。如果是这样,如果不重新运行另一个方法已经运行或等待的异步方法,那么多依赖场景将如何发挥作用?我猜是一些共享变量?

更新:

// ...行应该表明任务正在做一些事情,比如查找DB记录。如果我说得不清楚,很抱歉。如果上下文没有预热,大约一半的任务(总共8个)可能需要5秒才能运行,而另一半任务只是收集/组装/处理/使用该数据。

对于相互交织的语句,async/await是否比Task更有优势?

我还需要回滚以前的东西,如果有什么东西坏了,像删除数据库行,如果电子邮件发送失败,所以有几个级别的尝试/捕获在那里。

你会发现async/await(与Task.Run配对而不是StartNew)将使你的代码更清晰:

var x = await Task.Run(() => {
  ...
  return ...;
});
var y = await Task.Run(() => {
  ...
  return x + n;
});
var bazTask = Task.Run(() => {
  ...
  return y - n;
});
var bagTask = Task.Run(async () => {
  ...
  var g = y;
  var h = await bazTask;
  return 1;
}
await bagTask;
return "yay";

如果您想要await完成多个任务,您还可以选择使用Task.WhenAllawait的错误处理尤其清晰,因为它没有在AggregateException中包装异常。

然而

通过AJAX请求调用的

这是个小问题。StartNewTask.Run在ASP.NET上都应该避免。

缩短墙体时间2秒

是的,在ASP上并行处理。. NET(这就是代码当前所做的)将使单个请求执行得更快,但以牺牲可伸缩性为代价。如果服务器对每个请求都进行并行处理,那么服务器将无法处理尽可能多的请求。

保存到数据库,发送电子邮件,在其他api/数据库中查找一堆信息

这些都是I/o受限的操作,而不是cpu受限的操作。因此,理想的解决方案是创建真正异步的I/O方法,然后使用await(必要时使用Task.WhenAll)调用它们。通过"真正异步",我的意思是调用底层异步api(例如,HttpClient.GetStringAsync而不是WebClient.DownloadString;或实体框架的ToListAsync而不是ToList等)。使用StartNewTask.Run就是我所说的"伪异步"。

一旦你有了异步api,你的顶层方法真的变得简单了:

X x = await databaseService.GetXFromDatabaseAsync();
Y y = await apiService.LookupValueAsync(x);
Task<Baz> bazTask = databaseSerivce.GetBazFromDatabaseAsync(y);
Task<Bag> bagTask = apiService.SecondaryLookupAsync(y);
await Task.WhenAll(bazTask, bagTask);
Baz baz = await bazTask;
Bag bag = await bagTask;
return baz + bag;