如果您使用Scala期货,应该避免的错误

2021-02-21 12:14:06

好的,我之前已经写过一篇有关Scala Future的文章,但是那是针对某个特定错误的。这是您在编码或查看Scala Future代码时可能会遇到的或遇到的一些错误的集合。

关于我的一些事情-在过去的4年中,我在一家大型旅行公司中担任Scala开发人员,我已经看到我和我的每一个新加入同事都经历了这样的错误。我在公司内部举办了一些研讨会,现在将其推广到全世界。

所以就这样了..和乔治和玛莎(来自SICP书中的假想人们,他们现在正在学习Scala。)

像这样在Scala Future周围使用Await意味着George尚未使用异步编程。

来自Java世界,George习惯了命令式编程,这意味着他需要掌握变量。因此,他安排了Future,等待它得到结果,以便他可以使用它。但是,根本不使用Future有什么意义呢?

乔治需要学习的是不同的想法。与其强制性地对价值进行操作,不如将其操作提交给未来,以对另一个未来产生的价值进行操作。这就像将终结者发送到未来而不是过去执行某人;)

好的。现在聪明的玛莎已经想到了这一点,并且从未犯过这样的错误。但她认为在单元测试中始终使用Await是可以的。我在这里告诉她,这不是不!

单元测试应该运行得非常快。某些纯粹的TDD怪胎甚至在源代码发生更改时,就使单元测试始终自动运行。

在单元测试中到处都是Await或Thread.sleep会不必要地阻塞线程,并延长单元测试的持续时间。

那有什么解决方案?当Readyor最终取决于您的测试框架(Specs2或Scalatest)时使用.awaitor

有时,您可能必须使用未为现代世界更新的旧框架。

例如。 Quartz Scheduler?我不确定它是否已更新,但是它曾经希望您实现某些接口,并且仅在方法返回时才将作业标记为完成。

在上面的示例中,如果您在该作业中放置了异步API,那么Quartz会安排该作业并将其标记为完成,而实际的作业可能仍在运行。如果该作业是长期运行的作业,则可能会出现问题。如果调度程序在两者之间被终止,那么工作就会丢失,并且您永远不会知道。

因此,您需要使用Await来阻止框架,直到真正完成作业为止。像这样..

乔治和玛莎正在为一个核电厂进行项目。如果玛莎不审查防止灾难的代码,乔治就会成名。

这导致了不稳定的单元测试失败,因为即使在waterSupply.turnOn()之后是creaseVolumeControl(),也是如此。有时在那之前发生。

多亏玛莎(Martha)过去的类似错误经验,乔治现在才从她那里得知,在异步编程中,执行顺序可以是不确定的,除非您将期货一个接一个地明确地链接到适当的流程中(了解有关Monads的更多信息)。

George现在正在编写一个低延迟的高速API项目,并且由于以前的经验,他认真地将API流程中的所有期货链接在一起,以确保在请求处理结束时按顺序完成所有操作。这是他的API流程。

现在,这是一个不错的代码。单元测试将执行API,并且在将来准备就绪时,将按照代码中的确切顺序执行流程中的所有组件。

但是George正在考虑一些事情。.他意识到异步流完成所需的时间包括缓存服务更新缓存所需的时间!不必让API用户等待直到更新缓存服务。

因此,乔治决定从主API流程中拔出“更新缓存”,并使其异步发生..可能在其自己的ExecutionContext中。

现在好多了。我们已从主流中拔下cacheService.setCacheForDeduplication(dedupedRequest)。注意,他将<-更改为=。它减少了他的API的大量延迟!

单元测试也没有问题。只需使用具有足够超时的final块来验证一次cacheService方法是否被调用。

错误5:仅根据方法的Future [T]返回类型来假定方法的行为

George仍在从事高速低延迟API项目,并且他仍在寻找减少请求处理时间的机会。

他多次遍历自己的代码,找不到机会。然后,他开始更加仔细地研究他之前几次忽略过的某个库API。

“啊!他认为,“这只是一个异步代码!”即使在主api流中也没有问题,因为它只是一个单独的Future(类似于该缓存服务方法)。还是?他想确定。

利用他心爱的Intellij的Command + Click功能,他尝试深入研究代码。

他最终发现该方法在安排其他将来并返回之前正在执行大量CPU操作!!!

经过深思熟虑,乔治来到了一个顿悟,您无法根据返回类型判断方法。

1.该方法的哪一部分将在当前线程中运行; 2.哪些部分被安排为将来的操作;或3.它只是使用Future.successful(something)进行了简单的同步操作

他对自己的实现感到高兴,并四处向同事讲述。但是玛莎问他到底是怎么解决的?

然后,他再次思考,并提出了此解决方案:不要相信该库,而只是将其包装到我们自己的Future中,可能会使用其专用的ExecutionContext。

最后一个简单的。在大多数情况下,这是一个无害的错误,但这仍然意味着George对其代码不小心。

有时,我们处于Future上下文中,只是想为Monadic属性取一个值到Future Monad中,或者适合预期的类型,或者在单元测试中与模拟一起使用。

Future.apply()的作用是使用您的代码块(在这种情况下是简单的值或异常)创建一个Runnable,将该Runnable安排到线程池队列中,该线程池可能已经在忙于处理多个作业,并将解决该问题。 只有在完成所有先前的作业后才能返回值! 当您已经拥有需要返回的价值时,这会浪费很多时间。 根据上下文,这可能会影响您的应用程序延迟。 因此,乔治应该只使用这种举重方法,而无需花费额外的时间。 现在就这样。 感谢您耐心阅读这篇长文章! :) 我仍然有更多与Scala期货相关的陷阱,并将很快再发表另一篇文章。 如果您对我的写作有任何反馈意见,不胜感激,那么下一个可能会更好。 :)