async/await 是否会为交织语句提供优于 Task 的优势?

Would async/await provide benefit over Task for intertwined statements?

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

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";
}

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

更新:

// ... 行应该表示该任务正在做某事,比如查找数据库记录。抱歉,如果不清楚。如果上下文没有预热,大约一半的任务(总共 8 个)最多可能需要五秒 运行,而另一半的任务只是 collect/assemble/process/use 该数据。

I also need to roll back previous things if something breaks, like remove a database row if the email fails to send, so there are a few levels of try/catches in there.

您会发现 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.

中包装异常

不过

called via an AJAX request

这有点问题。在 ASP.NET.

上应避免 StartNewTask.Run

shaved off up to two seconds in wall time

是的,ASP.NET 上的并行处理(代码当前正在执行的操作)将使 单个 请求执行得更快,但 在可扩展性的代价。如果对每个请求进行并行处理,服务器将无法处理尽可能多的请求。

save to the database, send emails, look up a bunch of info in other APIs/databases

这些都是 I/O-bound 操作,不受 CPU 限制。因此 理想的 解决方案是创建真正异步的 I/O 方法,然后使用 await(如果需要,还可以使用 Task.WhenAll)调用它们。 "truly-async" 是指调用底层异步 API(例如,HttpClient.GetStringAsync 而不是 WebClient.DownloadString;或 Entity Framework 的 ToListAsync 而不是 ToList , ETC)。使用 StartNewTask.Run 就是我所说的 "fake asynchrony".

一旦你有了异步 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;