c# - 等待最后一次时不必要的异步/等待?

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

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

因为 async 只允许出现 await 这个词,所以我将把 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 ...
        }

当线程到达 await workTask; 时,它将方法拆分为 2 个部分。所以在 SimulateWork 完成后 - 方法的延续:又名://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 
        }

这里 - 我不需要需要 continuation ,意思是 - 我不需要 await 来拆分方法,这意味着 - 我不需要 async/await 完全在这里!但我仍然会得到相同的结果/行为!

所以我可以这样做:

   public void ProcessAsync()
            {
               SimulateWork();
            }

问题:

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

最佳答案

所以,你认为 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 的方式。是内部实现的。我发布了 detailed explanation of this behavior . async 通常不会发生这种情况Task/Task<>方法,其中异常存储在返回的 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 .

关于c# - 等待最后一次时不必要的异步/等待?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23382851/

相关文章:

c# - skynet 基准测试中的 TPL 样本并非真正并行?

asp.net - 是否使用 TPL 或 async/await

c# - c#问题中的正则表达式

c# - 使用DLLImport时,dll文件应该放在哪里?

c# - .Net Entity Framework 循环级联路径

c# - 应用程序关闭时的任务处理

c# - LINQ to SQL和SQLite-在UTF-8字符串中搜索

javascript - 是否可以将东西存储在 MVC Controller 中?

c# - 从 float 中获取 int 的正确方法是什么?

c# - pl/sql 查询和 .net 的奇怪行为