【问题标题】:MVar tryPut returns true and isEmpty also returns trueMVar tryPut 返回 true 并且 isEmpty 也返回 true
【发布时间】:2020-04-19 11:07:28
【问题描述】:

我编写了简单的回调(处理程序)函数,我将它传递给异步 api,我想等待结果:

object Handlers {

  val logger: Logger = Logger("Handlers")
  implicit val cs: ContextShift[IO] =
  IO.contextShift(ExecutionContext.Implicits.global)

  class DefaultHandler[A] {

  val response: IO[MVar[IO, A]] = MVar.empty[IO, A]

  def onResult(obj: Any): Unit = {
    obj match {
      case obj: A =>
        println(response.flatMap(_.tryPut(obj)).unsafeRunSync())
        println(response.flatMap(_.isEmpty).unsafeRunSync())
      case _ => logger.error("Wrong expected type")
    }
  }

  def getResponse: A = {
    response.flatMap(_.take).unsafeRunSync()
  }
}

但由于某种原因,tryPut 和 isEmpty(当我手动调用 onResult 方法时)都返回 true,因此当我调用 getResponse 时它会永远休眠。 这是我的测试:

class HandlersTest extends FunSuite {
    test("DefaultHandler.test") {
    val handler = new DefaultHandler[Int]
    handler.onResult(3)
    val response = handler.getResponse
    assert(response != 0)
    }
  }

有人可以解释为什么 tryPut 返回 true,但什么都没有。在 scala 中使用 Mvar/channels 的正确方法是什么?

【问题讨论】:

    标签: scala concurrency scala-cats side-effects


    【解决方案1】:

    “不安全”是一种暗示,但每次您拨打unsafeRunSync 时,您基本上都应该将其视为一个全新的宇宙。在您拨打电话之前,您只能描述将要发生的事情的说明,您实际上无法更改任何内容。通话期间是所有更改发生的时间。调用完成后,该 Universe 将被销毁,您可以读取结果但不再更改任何内容。一个 unsafeRunSync 宇宙中发生的事情不会影响另一个宇宙。

    您需要在测试代码中只调用一次。这意味着您的测试代码需要如下所示:

    val test = for {
      handler  <- TestHandler.DefaultHandler[Int]
      _        <- handler.onResult(3)
      response <- handler.getResponse
    } yield response
    assert test.unsafeRunSync() == 3
    

    请注意,与直接使用 MVar 相比,这并不能真正为您带来太多收益。我认为您正在尝试在 IO 内部和外部混合副作用,但这不起作用。所有的副作用都需要在里面。

    【讨论】:

    • 非常感谢,您的回答确实帮助我理解了为什么我的代码不起作用以及 IO 是如何工作的,但我不太明白我是如何在 IO 内部和外部混合副作用的。你能解释一下吗?
    • 任何时候您尝试使用从运行unsafeRunSync 获得的结果,例如当您在收到的handler 上调用onResult 时,它在IO 之外运行。跨度>
    • @KarlBielefeldt 感谢您对此进行调查,我的周末有点忙,我正要检查 OP 的问题是什么,但看来您已经解决了! :D - 只是为了确保,这个测试的更正是 OP 在我的代码中发布的 ScalaFiddle 上的那个,对吧?
    • 是的@Luis,我正在回复那个 ScalaFiddle。你让 OP 走上了正确的道路,但随后他将额外的 unsafeRunSync 移到了测试中。
    • @ibanezn04 效果系统的想法是跟踪您的所有效果。除了世界末日,您不应该调用任何不安全的函数。如果您使用IOApp,那么您将永远不需要调用其中任何一个。此外,对于您的用例,也许 Async 帮助器之一可以解决问题?我建议您打开一个新问题,详细解释您的问题并提供测试解决方案所需的所有代码。但在此之前,请务必阅读所有关于猫的文档和一些关于 IO 的博客和讨论,以便了解它是如何工作的。
    【解决方案2】:

    IO[X] 表示你有办法创建一些X。因此,在您的示例中,您正在输入一个 MVar,然后再输入另一个。

    我会这样做。

    object Handlers {
      trait DefaultHandler[A] {
        def onResult(obj: Any): IO[Unit]
    
        def getResponse: IO[A]
      }
    
      object DefaultHandler {
        def apply[A : ClassTag]: IO[DefaultHandler[A]] = 
          MVar.empty[IO, A].map { response =>
            new DefaultHandler[A] {
              override def onResult(obj: Any): IO[Unit] = obj match {
                  case obj: A =>
                    for {
                      r1 <- response.tryPut(obj)
                      _  <- IO(println(r1))
                      r2 <- response.isEmpty
                      _  <- IO(println(r2))
                    } yield ()
    
                  case _ =>
                    IO(logger.error("Wrong expected type"))
                }
    
              override def getResponse: IO[A] =
                response.take
            }
          }
      }
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-05
    • 1970-01-01
    • 2019-08-30
    • 2012-01-14
    • 1970-01-01
    • 1970-01-01
    • 2015-06-22
    • 1970-01-01
    相关资源
    最近更新 更多