You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:16:22