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

Roy*_*mir 15 .net c# task-parallel-library async-await c#-5.0

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

由于 async只允许单词等待出现,我将async不谈.

AFAIU等待着继续.而不是编写功能(连续)代码,编写同步代码.(我想把它称为可回调代码)

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

但看看这段代码:

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

 public    Task <string> SimulateWork()
        {
            return ...
        }
Run Code Online (Sandbox Code Playgroud)

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

一切都好

但是,如果方法是:

public async  Task ProcessAsync()
        {
           Task<string> workTask = SimulateWork();
           await workTask; //i don't care about the result , and I don't have any further commands 
        }
Run Code Online (Sandbox Code Playgroud)

在这里-我并不需要延续,意思是-我不需要await分割方法,这意味着-我不需要异步/等候在这里了! 而且我仍然会有相同的结果/行为!

所以我可以这样做:

   public void ProcessAsync()
            {
               SimulateWork();
            }
Run Code Online (Sandbox Code Playgroud)

题:

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

nos*_*tio 14

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

public async Task ProcessAsync()
{
    Task<string> workTask = SimulateWork();
    await workTask; //i don't care about the result , and I don't have any further 
}
Run Code Online (Sandbox Code Playgroud)

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

public async Task ProcessAsync()
{
    await Task.Delay(1000);
    Task<string> workTask = SimulateWork();
    return workTask; 
}
Run Code Online (Sandbox Code Playgroud)

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

public Task ProcessAsync()
{
    Task<string> workTask = SimulateWork();
    return workTask; 
}
Run Code Online (Sandbox Code Playgroud)

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

因此,如果您的调用者代码已准备好应对异常传播中的这些差异,那么最好跳过async/await任何可能的地方并简单地返回Task.

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

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

readonly object _syncLock = new Object();
readonly HashSet<Task> _pendingTasks = new HashSet<Task>();

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,
        // e.g.: await Task.WhenAll(_pendingTasks.ToArray())
        return;
    }

    // remove the successfully completed task from the list
    lock (_syncLock)
        _pendingTasks.Remove(task);
}
Run Code Online (Sandbox Code Playgroud)

你这样称呼它:

public Task ProcessAsync()
{
    QueueTaskAsync(SimulateWork());
}
Run Code Online (Sandbox Code Playgroud)

目标是在当前线程的同步上下文中立即抛出致命异常(例如,内存不足),同时延迟任务结果/错误处理直到适当.

还有的是利用工作的一个有趣的讨论,发射后不管这里.


i3a*_*non 10

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

public Task ProcessAsync()
{
    // some sync code
    return SimulateWork();
}
Run Code Online (Sandbox Code Playgroud)

这样你就不会"支付"标记方法的开销,async但你仍然有能力等待整个操作.


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

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