您在问题中问了几个问题。我将把它们分解成与您稍有不同的方法。但是首先让我直接回答这个问题。
我们都想要一台轻巧,高质量和便宜的相机,但是俗话说,这三者中最多只能有两个。您在这里处于相同情况。您需要一个高效,安全并且在同步和异步路径之间共享代码的解决方案。您只会得到其中两个。
让我解释一下为什么。我们将从这个问题开始:
我看到有人说您可以GetAwaiter().GetResult()
在异步方法上使用并从同步方法中调用它?在所有情况下该线程都安全吗?
这个问题的重点是“我是否可以通过使同步路径仅在异步版本上进行同步等待来共享同步和异步路径?”
在这一点上,让我非常清楚,因为它很重要:
您应该立即停止从这些人那里获得任何建议。
那是非常糟糕的建议。除非您有证据表明任务已正常完成或异常完成,否则从异步任务中同步获取结果是非常危险的。
这是非常糟糕的建议,原因是考虑这种情况。您想割草,但是割草机刀片坏了。您决定遵循以下工作流程:
- 从网站订购新刀片。这是一个高延迟的异步操作。
- 同步等待-即入睡,直到手握刀片。
- 定期检查邮箱,以查看刀片是否到达。
- 从包装盒中取出刀片。现在您已经掌握了它。
- 将刀片安装在割草机中。
- 修剪草坪。
怎么了?您将永远睡不着,因为现在检查邮件的操作被限制在邮件到达后发生的事情上。
这是非常容易陷入这种情况,当你同步等待上的任意任务。该任务可能在正在等待的线程的将来安排了工作,而由于您正在等待它,所以将来永远也不会到达。
如果您执行异步等待,那么一切都很好!您定期检查邮件,而在等待期间,您做三明治或纳税或其他任何事情;您可以在等待时继续完成工作。
切勿同步等待。如果任务完成,则没有必要。如果任务未完成但计划在当前线程上运行,则效率低下,因为当前线程可能正在为其他工作提供服务,而不是等待。如果任务未完成,并且调度在当前线程上运行,则挂起它以同步等待。没有充分的理由再次进行同步等待,除非您已经知道任务已完成。
有关此主题的更多信息,请参见
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
斯蒂芬比我能更好地解释现实情况。
现在让我们考虑“其他方向”。我们是否可以通过使异步版本仅在工作线程上执行同步版本来共享代码?
由于以下原因,这可能是并且确实可能是一个坏主意。
如果同步操作是高延迟的IO工作,则效率很低。从本质上讲,这雇用了一名工人,并使该工人入睡直至完成任务。线程异常昂贵。默认情况下,它们最少占用一百万字节的地址空间,它们会花费时间,并占用操作系统资源。您不想烧掉线程做无用的工作。
同步操作可能不是写为线程安全的。
如果高延迟工作受处理器限制,则这是一种更合理的技术,但是如果是这样,那么您可能不希望将其交给工作线程。您可能想要使用任务并行库将其并行化到尽可能多的CPU,您可能想要取消逻辑,并且不能简单地使同步版本执行所有操作,因为那样的话,它已经是异步版本了。
进一步阅读;斯蒂芬再次非常清楚地解释了这一点:
为什么不使用Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Task.Run的更多“做与不做”方案:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
那给我们留下了什么呢?两种共享代码的技术都会导致死锁或效率低下。我们得出的结论是,您必须做出选择。您是否想要一个高效,正确并能使调用者满意的程序,还是想节省一些通过在同步和异步路径之间复制少量代码而引起的击键?恐怕你们不会两者兼得。