仅在 return 语句处等待时将方法标记为 async 是不是有用? [复制]

Posted

技术标签:

【中文标题】仅在 return 语句处等待时将方法标记为 async 是不是有用? [复制]【英文标题】:Is it useful to mark a method async when it only awaits at the return statement? [duplicate]仅在 return 语句处等待时将方法标记为 async 是否有用? [复制] 【发布时间】:2017-06-10 20:43:42 【问题描述】:

只有下面方法的最后一行使用了'await',就在方法返回之前,所以这是否意味着该方法基本上是同步的,应该只调用“Get()”而没有 async 修饰符和后缀异步?

public virtual async Task<TEntity> GetAsync(Guid id)

    // some more code here
    return await _dbSet.FindAsync(id);

【问题讨论】:

Optimize an async method that ends "return await e" to be non-async "return e" #1981 讨论了一些微妙之处(特别是关于异常)。在大多数情况下,如果它是唯一的await,我会将其描述为反模式。 @Damien_The_Unbeliever 把它作为一个答案,这将是非常有益的。 【参考方案1】:

这不是说方法基本上是同步的

没有。它是异步的。您可能正在考虑 sequential (从一件事进展到另一件事),而不是 同步 (阻塞当前线程)。 await 将暂停方法(顺序)但不会阻塞线程(异步)。更多信息,请查看我的async intro。

没有 async 修饰符

虽然您可以省略 async/await 关键字,但我建议您不要这样做。这是因为// some more code here 可能会抛出异常。我在 eliding async and await 上的博客文章中介绍了这一点和其他注意事项。

以及后缀 Async?

不,该后缀适用于任何返回可等待的方法(例如,Task)。因此,即使您省略了asyncawait,它仍然会返回一个应该等待的任务,因此它应该仍然具有Async 后缀。

你可以这样想:Async 后缀是 API 接口的一部分。 async 关键字是一个实现细节。他们经常一起去,但并不总是。

【讨论】:

【参考方案2】:

这是一个见仁见智的问题。通常,命名约定是返回 TaskTask&lt;T&gt; 后缀为 Async 的任何内容。

你上面的例子最好这样写,这样你就没有异步包装器的额外开销。这是因为您不需要等待该方法中的结果。如果直接使用该方法,则该方法的使用者将等待结果。

public virtual Task<TEntity> GetAsync(Guid id, params Expression<Func<TEntity, object>>[] includeProperties))

    // some more code here
    return _dbSet.FindAsync(id);

【讨论】:

但即使它返回TaskTask&lt;T&gt;,也并不意味着它一定是异步的,对吧?这个问题我一直很纠结。 ...即如果你等待它? “真正的”异步操作(如磁盘读取,在内核深处是异步的)怎么样?船不是已经在异步的情况下航行了吗?也就是说,如果您调用方法的异步风格(例如ReadLineAsync),就不能使它们异步? 不,返回Task 表示已经开始的工作单元。 (除非你很生气并且正在返回冷任务,但没有人应该这样做,如果他们利用async,他们肯定不会这样做) @Damien_The_Unbeliever ^--- 是的! @Damien_The_Unbeliever -- 但如果它已经启动,并且返回它的方法没有使用async 调用,那么它在任务完成之前不会返回该任务吗?【参考方案3】:

await 不会阻止。它等待异步操作完成。这意味着原始线程在等待时被释放。在桌面应用程序中,这意味着 UI 线程在等待 HTTP 或数据库调用完成时被释放。

该操作完成后,执行将返回到原始线程(对于桌面应用程序)。

实际上,您可以通过删除 asyncawait 关键字并立即返回任务来简化代码。代码不会对操作的结果做任何事情,所以它不需要等待它完成。这将由方法的调用者完成:

public virtual Task<TEntity> GetAsync(Guid id)

    // some more code here
    return _dbSet.FindAsync(id);

这段代码中真正的异步操作是FindAsyncasync 关键字只是语法糖,允许您使用 await 关键字。如果你不需要await,你也不需要async关键字

【讨论】:

【参考方案4】:

在很多情况下,使用 Async 为每个异步方法添加后缀是多余的。如果您的方法返回TaskTask&lt;T&gt;,并且其目的是从数据库或其他网络资源中获取数据,那么它当然是异步运行的。如果您忘记等待某些东西,编译器也几乎总是会给您一个错误。考虑到这一点,有一些很好的理由使用 Async 为异步方法添加后缀:

该方法异步运行但不返回Task 还有另一种同步运行的同名方法 在调用代码时忘记等待不太可能被编译器捕获

使用Async 为方法名称添加后缀意味着该方法是异步运行的,该方法就是这样做的。无论是使用async/await 还是直接返回另一个Task 都是实现细节。想象一下,您正在查看一些调用忘记使用 await 的方法的代码。以下哪项使错误更明显?

var item1 = _example.Get(itemId1);

var item2 = _example.GetAsync(itemId2);

没有其他依据,没有理由相信第一行有什么问题。第二行至少会引起一些人的注意。

【讨论】:

异步方法应始终返回Taskasync void 应该保留给事件处理程序,我不能说我会“手动”调用它们。您还有其他异步运行但不返回任务的方法示例吗? 不确定在调用代码时忘记等待也不太可能被编译器捕获。如果调用方法是 async 并且您调用了 Task 方法而没有 await 您将收到警告。你能解释一下你的意思吗? 如果有 using SomeType = Task&lt;SomeOtherType&lt;List&lt;string&gt;&gt;&gt; 之类的 using 声明,则方法返回任务可能并不明显。如果调用代码也未标记为异步,您可以忘记等待而不会被编译器警告。

以上是关于仅在 return 语句处等待时将方法标记为 async 是不是有用? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

将参数传递给方法时将标记查询强化为 sqlInjection

lock关键字

Java基础教程(12)--深入理解类

仅在编辑单击时将 ReadOnly 属性设置为 devexpress 网格(MVC)的列

WCF 仅在满足特定条件时调用方法,否则等待

RestKit - 仅在第二次调用时将缓存保存到核心数据