await是最后一个时不必要的asyncawait?

问题描述 投票:16回答:2

我一直在处理很多关于 异步等待 最近(读了所有可能的文章,包括Stephen和Jon的最后2章),但我已经得出结论,我不知道它是否100%正确。- 因此,我的问题。

因为 async 只允许存在等待这个词,我就不说了。async 撇开。

AFAIU, await是关于延续的.与其写功能型(延续型)代码,不如写同步型代码。(我喜欢把它称为 回调'能 编码)

所以当编译器到达 await - 它将代码分成两部分,并将第二部分注册在第一部分完成后执行 ( 我不知道为什么这个词 callback 不使用 - 这正是我们要做的事情). (同时工作--线程又开始做其他事情了)。

但看这段代码。

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

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

当线程到达 await workTask; 它把方法分成了两部分。SimulateWork 完成--继续执行方法:又名.A。//do something with st - 被执行。

一切OK

但是 如果方法是 。

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 来拆分这个方法,这意味着--我不需要异步的 await 在这里! 我还是会有 同理 !

所以我可以这样做。

   public void ProcessAsync()
            {
               SimulateWork();
            }

问题:

  • 我的诊断是否100%正确?
c# .net task-parallel-library async-await c#-5.0
2个回答
16
投票

所以,你认为 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 
}

首先,我认为,根据 "当 await 是最后的" 你的意思是 "当 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 是内部实现的。我发了一个 详解. 这种情况通常不会发生在 async TaskTask<> 方法,其中异常存储在返回的 Task 对象。它仍然可能发生在一个 async void 方法,但那是一个 异曲同工.

所以,如果你的调用者代码已经准备好接受这样的异常传播差异,那么最好跳过 async/await 无论你在哪里,只需返回一个 Task 而不是。

另一个问题是,如果你想发出一个 弃之不顾 召唤. 通常情况下,你还是希望以某种方式跟踪已发射任务的状态,至少是为了处理任务异常。我无法想象,如果任务从未完成,我真的不会在意,即使它所做的只是记录。

所以,对于fire-and-forget,我通常会使用一个帮助程序 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());
}

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

关于使用带有fire-and-forget的任务,有一个有趣的讨论。此处.


11
投票

你很接近了。这意味着你可以这样写。

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

这样你就不用为将方法标记为... async 但你仍然保持着等待整个操作的能力。

© www.soinside.com 2019 - 2024. All rights reserved.