使用异步避免重复代码
Posted
技术标签:
【中文标题】使用异步避免重复代码【英文标题】:Avoid duplicate code with Async 【发布时间】:2014-07-18 22:22:01 【问题描述】:如何避免为异步和非异步方法编写两次相同的代码。 我目前正在使用 ASP.NET,所以我目前在请求线程上,我很快了解到他下面的代码(应该显示我的意图),绝对是错误的做法。
应用程序死锁,因为 await 关键字试图返回 .Result 阻塞的同一线程。
我这样做的全部原因是为了避免两次编写相同的“FindAll”代码。
public IEnumerable<Resource> FindAll()
return FindAllAsync().Result;
public async Task<IEnumerable<Resource>> FindAllAsync()
return await Context.Resources.ToListAsync();
那么你如何解决这个问题?
【问题讨论】:
可以说,除非您想支持取消,否则您不需要同步和异步。给定IEnumerable<T>
的延迟迭代模型,任何迭代的取消都是免费的。
可以说我可以再举一个例子,返回另一个对象,但问题仍然存在。这个问题不是关于 IEnumerable 而是从同步上下文调用异步方法。
为什么需要这两种方法? (另外,请考虑根本不使用异步数据库查询。您可以在同一个应用程序中混合使用同步和异步,没有任何劣势。)
@Gusdor。这是我的问题,我并不是说你不对,但我的问题不是关于 IEnumerable,而是我在使用 async/await 时面临的挑战。无论返回类型如何,返回类型都不会改变上述代码会导致死锁的事实。
@usr 请提供一个答案,并举例说明您的建议。
【参考方案1】:
如何避免为异步和非异步方法编写两次相同的代码。
一般情况下你不能。
有问题的操作要么是自然异步的,要么是自然同步的。在这个例子中(一个数据库请求),它自然是异步的。因此,使 API 异步。就是这样。
Stephen Toub 有一对著名的博文 Should I expose synchronous wrappers for asynchronous methods? 和 Should I expose asynchronous wrappers for synchronous methods? 两个问题的简短回答都是“不”。
您可以通过各种 hack 来公开这两种类型的 API(Stephen 在他的帖子中介绍了每种方法),但与缺点相比,优点微不足道。
【讨论】:
谢谢斯蒂芬,这确实结束了讨论,并且很好地解释了它。 如果你保持 DRY 并且不编写自然异步操作的同步版本,例如数据库代码,但您还需要从同步代码中使用它——比如控制台应用程序——我猜你使用.GetAwaiter().GetResult()
。
更新链接:我应该为异步方法公开同步包装器吗?devblogs.microsoft.com/pfxteam/…我应该为同步方法公开异步包装器吗?devblogs.microsoft.com/pfxteam/…
@MiguelMarques:谢谢!我用您提供的新链接更新了答案。【参考方案2】:
同步和异步方法的行为应该不同。通常这意味着同步调用应该调用一个作为请求的一部分阻塞的 API,而异步应该调用“一直异步”的 API
如果你不想创建完全同步的api,在你的情况下,你可以使用ConfigureAwait(false)
。
当你用ConfigureAwait(false)
标记Task
时,你实际上是在说“没有必要在同一个await
内运行延续(await
之后的代码),你可以在里面完成您当前的上下文(通常是 ThreadPool 线程)"
至于你的第二种方法,你可以去掉async
关键字,省去冗余生成的状态机:
public IEnumerable<Resource> FindAll()
return FindAllAsync().Result;
public Task<IEnumerable<Resource>> FindAllAsync()
return Context.Resources.ToListAsync();
一些阅读参考:
Stephan Cleary - Dont block on async code
Best practice to call ConfigureAwait(false)
Task.ConfigureAwait(false) MSDN
【讨论】:
当我真正想从上到下使用 async/await 时,这不会损害 async 的好处吗? 你会说痛吗?当您必须等待并稍后做更多工作时,您应该将方法标记为async
。如果不需要等待,您可以简单地返回Task
。现在,无论谁调用FindAllAsync
可能仍然是await
返回的Task
我只是不完全理解 ConfigureAwait 的作用,以及它是否有缺点?在我看来,在 webrequest 线程上调用它似乎是个坏主意。
这避免了死锁,但并不能避免使用异步 IO 然后等待引入的性能损失。 FindAll 方法是两种风格中最差的。尽管如此,这仍然是一种有效的代码重用方式。
@YuvalItzchakov 很好的回答 Yuval,谢谢 :) 我明白这不是最高性能的代码,但我想知道如何应对这一挑战。以上是关于使用异步避免重复代码的主要内容,如果未能解决你的问题,请参考以下文章