Как Задача вызывающей стороны избежать того, чтобы ее планировщик изменился подзадачей, над которой она перешла
Я ищу способ, которым Task
(т. е. внешняя область) может выполнить "подзадачу", используя flatMap
или что-то эквивалентное и убедитесь, что любые последующие вызовы в цепочке во внешней области используют оригинальный планировщик.
Используемые библиотеки и скалы:
- Скала - 2.12.4
- моникс -
"io.monix" %% "monix" % "3.0.0-RC1"
- кошки -
"org.typelevel" %% "cats-core" % "1.0.1"
Пример кода:
import monix.eval.Task
import monix.execution.Scheduler
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import monix.execution.Scheduler.Implicits.global
import cats.implicits._
object Test extends App {
val io1 = Scheduler.io("io1")
val io2 = Scheduler.io("io2")
def taskEval(name: String) = Task.eval(println(s"Running eval Task [$name] on thread [${Thread.currentThread().getName}]"))
def subTask: Task[Unit] = {
taskEval("subTaskScope").executeOn(io2)
}
def outerScope(sub: Task[Unit]): Task[Unit] = {
taskEval("outerScopeBefore") *> sub *> taskEval("outerScopeAfter")
}
def outerScopeTryProtect(sub: Task[Unit]): Task[Unit] = {
taskEval("outerScopeBefore") *> (sub <* Task.shift) *> taskEval("outerScopeAfter")
}
val program1 = taskEval("programBefore").executeOn(io1) *> outerScope(subTask) *> taskEval("programAfter")
val program2 = taskEval("programBefore").executeOn(io1) *> outerScopeTryProtect(subTask) *> taskEval("programAfter")
Await.result(program1.runAsync, Duration.Inf)
// Running eval Task [programBefore] on thread [io1-573]
// Running eval Task [outerScopeBefore] on thread [io1-573]
// Running eval Task [subTaskScope] on thread [io2-574]
// Running eval Task [outerScopeAfter] on thread [io2-574] // << we don't shift back so we are stuck with the scheduler that is forces by subTask
// Running eval Task [programAfter] on thread [io2-574]
println("------")
// Running eval Task [programBefore] on thread [io1-573]
// Running eval Task [outerScopeBefore] on thread [io1-573]
// Running eval Task [subTaskScope] on thread [io2-574]
// Running eval Task [outerScopeAfter] on thread [scala-execution-context-global-575] // we shift the scheduler but this restores the default scheduler
// Running eval Task [programAfter] on thread [scala-execution-context-global-575]
Await.result(program2.runAsync, Duration.Inf)
}
subTask
метод хочет выполнить некоторую асинхронную работу на выделенном планировщике (io2
), поэтому он заставляет асинхронную границу планировщика, используя executeOn
,
outerScope
метод выполняется в какой-то программе program1
и это вызывает sub
(т.е. subTask
) с помощью flatMap
, Поскольку это не относится ни к какой явной асинхронной границе, если subTask
случается, чтобы изменить планировщик (что он делает), остальные outerScope
будет использовать планировщик, измененный subTask
, По этой причине звонок в taskEval("outerScopeAfter")
выполняется на io2
планировщик.
outerScopeTryProtect
пытается защитить планировщик, который он использует, вводя асинхронную границу (используя Task.shift
) после flatMap
PED sub
(т.е. subTask
). Тем не менее, асинхронная граница (Task.shift
) сбрасывает планировщик в планировщик по умолчанию, который в этом случае будет полностью возвращен к тому, который неявно использовался в program2.runAsync
, Это не то, что мы хотим, так как мы хотели бы вернуться к планировщику, который использовался при вызове taskEval("outerScopeBefore")
т.е. планировщик io1
,
Я ищу что-то вроде Task[A].flatMap[B](f: A => Task[B]): Task[B]
что бы выполнить задачу, произведенную f
в любом случае f
указывает (возможно, с использованием другого планировщика), но в результате Task
из flatMap
вызов вернется к планировщику, используемому Task[A]
перед flatMap
,