当等待是最后一次时,不必要的异步/等待?
我最近一直在处理异步等待 (阅读包括斯蒂芬和乔恩最后两章在内的所有可能的文章),但我得出结论,我不知道它是否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
常见错误的研究:
- Fire-Forget异步方法
- 不必要的异步方法
- 异步方法下长时间运行的操作
- 在异步方法下不必要地捕获上下文