MVar tryPut возвращает значение true, а isEmpty также возвращает значение true.

Я написал простую функцию обратного вызова (обработчика), которую я передаю асинхронному 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, но ничего не ставит. И как правильно использовать Mvar/channels в scala?


person ibanezn04    schedule 30.12.2019    source источник


Ответы (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
        }
      }
  }
}
person Luis Miguel Mejía Suárez    schedule 30.12.2019

«Небезопасно» — это своего рода подсказка, но каждый раз, когда вы вызываете unsafeRunSync, вы должны думать об этом как о совершенно новой вселенной. Прежде чем сделать звонок, вы можете только описать инструкции, что произойдет, вы не можете ничего изменить. Во время звонка происходят все изменения. Как только вызов завершится, эта вселенная будет уничтожена, и вы сможете прочитать результат, но уже не сможете ничего изменить. То, что происходит в одной unsafeRunSync вселенной, не влияет на другую.

Вам нужно вызвать его ровно один раз в тестовом коде. Это означает, что ваш тестовый код должен выглядеть примерно так:

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

Обратите внимание, что на самом деле это не дает вам многого, если вы просто используете MVar напрямую. Я думаю, вы пытаетесь смешивать побочные эффекты внутри IO и вне его, но это не работает. Все побочные эффекты должны быть внутри.

person Karl Bielefeldt    schedule 05.01.2020
comment
Большое спасибо, ваш ответ действительно помог мне понять, почему мой код не работает и как работает ввод-вывод, но я не совсем понял, как я смешиваю побочные эффекты внутри ввода-вывода и снаружи. Не могли бы вы объяснить немного больше об этом? - person ibanezn04; 06.01.2020
comment
Всякий раз, когда вы пытаетесь использовать результат, полученный при выполнении unsafeRunSync, например, когда вы вызывали onResult на полученном handler, это выполняется за пределами IO. - person Karl Bielefeldt; 06.01.2020
comment
@KarlBielefeldt Спасибо, что изучили это, мои выходные были немного загружены, я собирался проверить, в чем проблема ОП, но, похоже, вы уже ее исправили! : D - Просто чтобы убедиться, что это исправление теста - это то, что на ScalaFiddle, которое OP опубликовал с моим кодом, верно? - person Luis Miguel Mejía Suárez; 06.01.2020
comment
Да, @Luis, я отвечал на этот ScalaFiddle. Вы получили OP на правильном пути, но вместо этого он переместил в тест дополнительные unsafeRunSync. - person Karl Bielefeldt; 06.01.2020
comment
@ Луис, Карл Билефельдт, большое спасибо, ребята, за то, что помогли мне понять. Чтобы уточнить все это: 1) плохо смешивать побочные эффекты внутри и вне IO (например, работать с результатом запуска unsafeRunSync) (но почему?). 2) Мое решение с MVar кажется здесь неуместным. Что бы вы порекомендовали использовать в этой ситуации, может быть, получить Future из обработчика? (метод onResult ничего не должен возвращать, потому что обработчик расширяет класс библиотеки) (это src моего любимого проекта: github.com/ibanezn04/GreatNews/blob/public-master/src/main/< /а>) - person ibanezn04; 06.01.2020
comment
@ibanezn04 Идея системы эффектов состоит в том, чтобы отслеживать все ваши эффекты. Вы не должны вызывать какие-либо небезопасные функции, кроме как на конец света. И если вы используете IOApp, вам никогда не нужно будет вызывать ни один из них. Кроме того, для вашего варианта использования, возможно, один из помощников Async поможет? Я бы порекомендовал вам открыть новый вопрос, подробно объясняющий вашу проблему и предоставить весь код, необходимый для тестирования решения. Но перед этим обязательно прочитайте всю документацию по кошачьему эффекту, а также несколько блогов и разговоров об IO, чтобы получить представление о том, как это работает. - person Luis Miguel Mejía Suárez; 06.01.2020