使用异步避免重复代码

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&lt;T&gt;的延迟迭代模型,任何迭代的取消都是免费的。 可以说我可以再举一个例子,返回另一个对象,但问题仍然存在。这个问题不是关于 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,谢谢 :) 我明白这不是最高性能的代码,但我想知道如何应对这一挑战。

以上是关于使用异步避免重复代码的主要内容,如果未能解决你的问题,请参考以下文章

在循环中调用异步函数时避免重复创建函数

如何避免在JavaScript中使用.then()地狱? [重复]

以不同方式处理子类/避免代码重复

为啥我不能将异步代码作为同步运行 [重复]

在不使用多重继承的情况下避免代码重复

如何避免重复的 TextView 代码?