仅在 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
)。因此,即使您省略了async
和await
,它仍然会返回一个应该等待的任务,因此它应该仍然具有Async
后缀。
你可以这样想:Async
后缀是 API 接口的一部分。 async
关键字是一个实现细节。他们经常一起去,但并不总是。
【讨论】:
【参考方案2】:这是一个见仁见智的问题。通常,命名约定是返回 Task
或 Task<T>
后缀为 Async
的任何内容。
你上面的例子最好这样写,这样你就没有异步包装器的额外开销。这是因为您不需要等待该方法中的结果。如果直接使用该方法,则该方法的使用者将等待结果。
public virtual Task<TEntity> GetAsync(Guid id, params Expression<Func<TEntity, object>>[] includeProperties))
// some more code here
return _dbSet.FindAsync(id);
【讨论】:
但即使它返回Task
或Task<T>
,也并不意味着它一定是异步的,对吧?这个问题我一直很纠结。
...即如果你等待它? “真正的”异步操作(如磁盘读取,在内核深处是异步的)怎么样?船不是已经在异步的情况下航行了吗?也就是说,如果您调用方法的异步风格(例如ReadLineAsync
),就不能使它们不异步?
不,返回Task
表示已经开始的工作单元。 (除非你很生气并且正在返回冷任务,但没有人应该这样做,如果他们利用async
,他们肯定不会这样做)
@Damien_The_Unbeliever ^--- 是的!
@Damien_The_Unbeliever -- 但如果它已经启动,并且返回它的方法没有使用async
调用,那么它在任务完成之前不会返回该任务吗?【参考方案3】:
await
不会阻止。它等待异步操作完成。这意味着原始线程在等待时被释放。在桌面应用程序中,这意味着 UI 线程在等待 HTTP 或数据库调用完成时被释放。
该操作完成后,执行将返回到原始线程(对于桌面应用程序)。
实际上,您可以通过删除 async
和 await
关键字并立即返回任务来简化代码。代码不会对操作的结果做任何事情,所以它不需要等待它完成。这将由方法的调用者完成:
public virtual Task<TEntity> GetAsync(Guid id)
// some more code here
return _dbSet.FindAsync(id);
这段代码中真正的异步操作是FindAsync
。 async
关键字只是语法糖,允许您使用 await
关键字。如果你不需要await
,你也不需要async
关键字
【讨论】:
【参考方案4】:在很多情况下,使用 Async 为每个异步方法添加后缀是多余的。如果您的方法返回Task
或Task<T>
,并且其目的是从数据库或其他网络资源中获取数据,那么它当然是异步运行的。如果您忘记等待某些东西,编译器也几乎总是会给您一个错误。考虑到这一点,有一些很好的理由使用 Async
为异步方法添加后缀:
Task
还有另一种同步运行的同名方法
在调用代码时忘记等待不太可能被编译器捕获
使用Async
为方法名称添加后缀意味着该方法是异步运行的,该方法就是这样做的。无论是使用async/await
还是直接返回另一个Task 都是实现细节。想象一下,您正在查看一些调用忘记使用 await 的方法的代码。以下哪项使错误更明显?
var item1 = _example.Get(itemId1);
var item2 = _example.GetAsync(itemId2);
没有其他依据,没有理由相信第一行有什么问题。第二行至少会引起一些人的注意。
【讨论】:
异步方法应始终返回Task
。 async void
应该保留给事件处理程序,我不能说我会“手动”调用它们。您还有其他异步运行但不返回任务的方法示例吗?
不确定在调用代码时忘记等待也不太可能被编译器捕获。如果调用方法是 async
并且您调用了 Task
方法而没有 await
您将收到警告。你能解释一下你的意思吗?
如果有 using SomeType = Task<SomeOtherType<List<string>>>
之类的 using 声明,则方法返回任务可能并不明显。如果调用代码也未标记为异步,您可以忘记等待而不会被编译器警告。以上是关于仅在 return 语句处等待时将方法标记为 async 是不是有用? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
将参数传递给方法时将标记查询强化为 sqlInjection