【问题标题】:Unnecessary async/await when await is last?等待最后一次时不必要的异步/等待?
【发布时间】:2014-06-16 11:09:08
【问题描述】:

我最近处理了很多 async await(阅读所有可能的文章,包括 Stephen 和 Jon 的最后 2 章),但我得出了结论,我不知道是不是100% 正确。 - 所以我的问题。

由于async 只允许出现await 这个词,我将把async 放在一边。

AFAIU,await 就是关于 continuation 的。与其编写功能(连续)代码,不如编写同步代码。 (我喜欢将其称为 callback'able 代码)

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

但是看看这段代码:

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

 public    Task <string> SimulateWork()
        {
            return ...
        }

当线程到达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 
        }

这里 - 我 不需要 需要继续,意思是 - 我不需要 await 来拆分方法,这意味着 - 我不需要 async /await 这里全部!,我仍然会有相同的结果/行为

所以我可以这样做:

   public void ProcessAsync()
            {
               SimulateWork();
            }

问题:

  • 我的诊断是否 100% 正确?

【问题讨论】:

  • 要 100% 完全重写,您仍然应该返回 Task 而不是 void 并且应该返回由 SimulateWork 返回的 Task - 这样,调用者 ProcessAsyncawait(或 Wait)仍然可以在 Task 上知道您何时完成。
  • 您应该始终关心任务的完成方式。
  • @PauloMorgado 我不同意这种说法。在我的示例中,只有当我需要知道 ProcessAsync 发生了什么时,它才是相关的。在某些情况下,火灾和遗忘是合法的。
  • 任务错误结束会怎样?
  • 就像执行后台线程时一样,您将在该线程内处理错误。 ( 试着抓)。如果关于延续和 A 取决于 B - 那么就像我说的 - 使用 await/continue with

标签: c# .net task-parallel-library async-await c#-5.0


【解决方案1】:

你已经接近了。这意味着你可以这样写:

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

这样您就不会“支付”将方法标记为async 的开销,但您仍然可以等待整个操作。

【讨论】:

  • @RoyiNamir 如果您指的是async void 事件处理程序,那么它们必须保持async 以支持异步操作,这与ProcessAsync 不同,async 在没有真正改变的情况下很容易被标记任何东西。
  • 是的,我知道,jsut 提到他们将无法拥有 Task 返回类型。
  • 要理解的重要部分是,您的 void 返回方法将是不可等待的,正如阿农所说。调用代码的用户将不知道该方法是基于void签名同步运行还是异步运行
【解决方案2】:

所以,您认为下面的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 
}

首先,我假设在 "when await is last" 下,您的意思是 "当 await 是唯一的 await "。必须是这样,否则下面的代码根本无法编译:

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

现在,如果是唯一await,你确实可以这样优化:

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

但是,它会给您带来完全不同的异常传播行为,这可能会产生一些意想不到的副作用。问题是,现在可能会在调用者的堆栈上抛出异常,这取决于 SimulateWork 在内部是如何实现的。我发布了detailed explanation of this behavior。这通常不会发生在 async Task/Task&lt;&gt; 方法中,其中异常存储在返回的 Task 对象中。 async void 方法仍然可能发生这种情况,但这是 different story

因此,如果您的调用方代码已准备好应对异常传播中的此类差异,最好尽可能跳过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);
}

你可以这样称呼它:

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

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

关于使用任务与即发即弃here 进行了有趣的讨论。

【讨论】:

    猜你喜欢
    • 2016-07-07
    • 2016-03-25
    • 2017-10-09
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    相关资源
    最近更新 更多