当等待是最后一次时,不必要的异步/等待?

我最近一直在处理异步等待 (阅读包括斯蒂芬和乔恩最后两章在内的所有可能的文章),但我得出结论,我不知道它是否100%正确。 – 因此我的问题。

由于async只允许单词等待存在,我将把async放在一边。

AFAIU等待着继续。 而不是编写function(连续)代码,写同步代码。 (我想将其称为可回调代码)

因此,当编译器到达await – 它将代码分成2个部分,并在第一部分完成后注册要执行的第二部分( 我不知道为什么不使用单词callback – 这正是所做的 ) 。 (同时工作 – 线程回来做其他事情)。

但看看这段代码:

 public async Task ProcessAsync() { Task workTask = SimulateWork(); string st= await workTask; //do something with st } public Task  SimulateWork() { return ... } 

当线程到达await workTask; 它将方法分为2个部分。 所以在SimulateWork完成之后 – 方法的继续:AKA: //do something with st执行//do something with st

一切都好

但是 ,如果方法是:

 public async Task ProcessAsync() { Task workTask = SimulateWork(); await workTask; //i don't care about the result , and I don't have any further commands } 

这里 – 我不需要继续,意思是 – 我不需要await分割方法,这意味着 – 我根本不需要异步/等待 而且我仍会有相同的结果/行为

所以我可以这样做:

  public void ProcessAsync() { SimulateWork(); } 

题:

  • 我的诊断程序是100%正确吗?

因此,您认为下面的await是多余的,因为问题标题暗示:

 public async Task ProcessAsync() { Task workTask = SimulateWork(); await workTask; //i don't care about the result , and I don't have any further } 

首先,我假设在“当await是最后一次”时,你的意思是“当await是唯一await 。 它必须是这样,因为否则以下就不会编译:

 public async Task ProcessAsync() { await Task.Delay(1000); Task workTask = SimulateWork(); return workTask; } 

现在,如果它是唯一的 await ,你可以像这样优化它:

 public Task ProcessAsync() { Task workTask = SimulateWork(); return workTask; } 

但是,它会为您提供完全不同的exception传播行为,这可能会产生一些意想不到的副作用。 问题是,现在可能会在调用者的堆栈上抛出exception,具体取决于SimulateWork在内部的实现方式。 我发布了这种行为的详细解释 。 这通常不会发生在async Task / Task<>方法中,其中exception存储在返回的Task对象中。 对于async void方法,它仍然可能发生,但这是一个不同的故事 。

因此,如果您的调用者代码已准备好应对exception传播中的这种差异,那么最好async/await任何地方跳过async/await而只需返回一个Task

另一个问题是,如果你想发出一个即发即弃的电话 。 通常,您仍希望以某种方式跟踪已触发任务的状态,至少是出于处理任务exception的原因。 我无法想象一个案例,我真的不在乎任务是否永远不会完成,即使它所做的只是记录。

因此,对于即发即弃,我通常使用辅助async void方法,该方法将待处理任务存储在某处以供稍后观察,例如:

 readonly object _syncLock = new Object(); readonly HashSet _pendingTasks = new HashSet(); async void QueueTaskAsync(Task task) { // keep failed/cancelled tasks in the list // they will be observed outside lock (_syncLock) _pendingTasks.Add(task); try { await task; } catch { // is it not task's exception? if (!task.IsCanceled && !task.IsFaulted) throw; // re-throw // swallow, but do not remove the faulted/cancelled task from _pendingTasks // the error will be observed later, when we process _pendingTasks, // eg: await Task.WhenAll(_pendingTasks.ToArray()) return; } // remove the successfully completed task from the list lock (_syncLock) _pendingTasks.Remove(task); } 

你这样称呼它:

 public Task ProcessAsync() { QueueTaskAsync(SimulateWork()); } 

目标是在当前线程的同步上下文中立即抛出致命exception(例如,内存不足),同时延迟任务结果/error handling直到适当。

这里有一个有趣的讨论,即使用即发即弃的任务。

你很亲密 这意味着你可以像这样写:

 public Task ProcessAsync() { // some sync code return SimulateWork(); } 

这样你就不会“支付”将方法标记为async的开销,但你仍然保持等待整个操作的能力。


PS: 这是一项关于async-await常见错误的研究:

  1. Fire-Forget异步方法
  2. 不必要的异步方法
  3. 异步方法下长时间运行的操作
  4. 在异步方法下不必要地捕获上下文