使用Cats Effect Ref作为缓存(第二部分):实例不一致问题及修复
Cats Effect Ref 作为缓存跨操作失效问题
首次运行(firstRun)中设置的值在第二次运行(secondRun)中无法读取,会出现“strange! no value found in secondRun”提示,核心原因是每次调用 cache.flatMap { ref => ... } 都会得到不同的Ref实例。
为什么会这样?
你定义的 cache 是 IO[Ref[IO, Option[String]]],本质上是一个“创建Ref的IO操作”。每次调用 cache,都会执行一遍 Ref.of[IO, Option[String]](None),也就是生成一个全新的、初始值为None的Ref。
firstRun和secondRun各自调用了cache.flatMap,相当于分别触发了两次Ref创建操作——它们操作的是两个完全独立的缓存实例,自然secondRun读不到firstRun设置的值。
怎么修复?
下面提供两种常用的修复方式:
方案1:提前创建Ref并传递
把Ref的创建逻辑移到run方法中,将同一个Ref实例直接传递给firstRun和secondRun,确保两者操作的是同一个缓存:
import cats.effect.kernel.Ref import cats.effect.{IO, IOApp} object SomeMain2 extends IOApp.Simple { override def run: IO[Unit] = { for { cache <- Ref.of[IO, Option[String]](None) _ <- firstRun(cache) _ <- secondRun(cache) } yield () } private def firstRun(cache: Ref[IO, Option[String]]): IO[Unit] = { val checkValueBeforeSet = cache.get.flatMap { case Some(v) => IO(println(v)) case None => IO(println("no value found in firstRun")) } val doSetAction = cache.set(Some("abc")).map(_ => println("set action done in firstRun")) val checkValueAfterSet = cache.get.flatMap { case Some(v) => IO(println(s"as expected value found: $v")) case None => IO(println("unexpected still no value set!")) } for { _ <- checkValueBeforeSet _ <- doSetAction _ <- checkValueAfterSet _ <- IO(println(s"ref: $cache")) } yield () } private def secondRun(cache: Ref[IO, Option[String]]): IO[Unit] = { val checkValue = cache.get.flatMap { case Some(v) => IO(println(v)) case None => IO(println("strange! no value found in secondRun")) } for { _ <- checkValue _ <- IO(println(s"ref: $cache")) } yield () } }
方案2:用Memoization缓存Ref创建操作
如果你想保持原有的cache定义方式,可以通过IO.memoize让cache只执行一次Ref创建逻辑,后续调用都会复用同一个实例:
import cats.effect.kernel.Ref import cats.effect.{IO, IOApp} object SomeMain2 extends IOApp.Simple { // 用memoize确保Ref只创建一次 lazy val cache: IO[Ref[IO, Option[String]]] = Ref.of[IO, Option[String]](None).memoize override def run: IO[Unit] = { for { _ <- firstRun _ <- secondRun } yield () } // firstRun和secondRun的代码无需修改 private def firstRun: IO[Unit] = cache.flatMap { ref => val checkValueBeforeSet = ref.get.flatMap { case Some(v) => IO(println(v)) case None => IO(println("no value found in firstRun")) } val doSetAction = ref.set(Some("abc")).map(_ => println("set action done in firstRun")) val checkValueAfterSet = ref.get.flatMap { case Some(v) => IO(println(s"as expected value found: $v")) case None => IO(println("unexpected still no value set!")) } for { _ <- checkValueBeforeSet _ <- doSetAction _ <- checkValueAfterSet _ <- IO(println(s"ref: $ref")) } yield () } private def secondRun: IO[Unit] = cache.flatMap { ref => val checkValue = ref.get.flatMap { case Some(v) => IO(println(v)) case None => IO(println("strange! no value found in secondRun")) } for { _ <- checkValue _ <- IO(println(s"ref: $ref")) } yield () } }
修复后输出
两种方案修复后,运行结果都会变成:
no value found in firstRun set action done in firstRun as expected value found: abc ref: cats.effect.kernel.SyncRef@xxxxxx abc ref: cats.effect.kernel.SyncRef@xxxxxx
内容的提问来源于stack exchange,提问作者jakstack
相关产品推荐
相关产品推荐

