英文:
Converting monix.eval.Task to scala.concurrent.Future
问题
我正在尝试重用一个使用 monix.eval.Task 用于异步任务的模块。
我的项目使用 scala.concurrent.Future。
我正在尝试理解将其转换为具有最小性能损失的 Future 的最佳方法。
最简单的方法是使用额外的线程池:
import monix.eval.Task
import monix.execution.Scheduler.Implicits.global
import scala.concurrent.Future
// 创建一个 Monix Task
val monixTask: Task[String] = Task("Hello, Monix!")
// 使用 toFuture 将 Task 转换为 Future
val scalaFuture: Future[String] = monixTask.toFuture
但我并不完全了解性能影响。
- 我已经在项目中定义了
scala.concurrent.ExecutionContext。添加全局的monix.execution.Scheduler会有什么影响? - 任务将在哪里执行?使用
ExecutionContext还是Scheduler?我猜想它们会在Scheduler上排队,具有小的转换开销,然后在ExecutionContext上运行?
给定的模块只是在所有接收到的 Future 上使用了 Task.deferFuture。
这些异步任务是 IO 任务,每秒处理约 300k 到 600k 个请求。
英文:
I’m trying to reuse some module that uses monix.eval.Task for async tasks.
My project uses scala.concurrent.Future.
I'm trying to understand the best way to convert it to Future with the least amount of damage.
The easiest way is to use an additional thread pool:
import monix.eval.Task
import monix.execution.Scheduler.Implicits.global
import scala.concurrent.Future
// Create a Monix Task
val monixTask: Task[String] = Task("Hello, Monix!")
// Convert the Task to a Future using toFuture
val scalaFuture: Future[String] = monixTask.toFuture
But I don't fully understand the performance implications.
- I already have
scala.concurrent.ExecutionContextdefined in the project. What are the implications of adding the globalmonix.execution.Scheduler? - Where the tasks will actually be computed? With
ExecutionContextorScheduler? I'm guessing it queue up onScheduler, have a small overhead of conversion, and then run onExecutionContext?
The given module simply uses Task.deferFuture on all the Future he receives.
The async tasks are IO tasks, having 300k~600k rpm.
答案1
得分: 4
Monix的默认调度器委托给Scala的默认ExecutionContext,因此,如果您想避免使用该EC,您应该定义自己的Scheduler实例,该实例使用您自定义的EC。Scheduler伴生对象定义了一些apply方法,用于此目的。
调度器使用ScheduledExecutorService来_调度_任务,并使用ExecutionContext来_运行_任务。因此,如果将您自定义的ExecutionContext传递给Scheduler.apply,您的任务将在其中运行。
关于Task和Future之间的互操作的一点说明:Task本质上是惰性的。构造一个Task不会立即运行它。因此,当将返回Future的表达式包装为Task时,deferFuture采用按名称调用的方式,因此直到要求运行该Task时,Future才会被构建和启动。
既然您提到Tasks包含IO(我假设您的意思是"I/O"意义上的,而不是cats.effect.IO),可能适合设置一个单独的专用线程池(ExecutionContext)来执行与IO相关的操作,以避免阻塞您的CPU绑定的"compute"线程。可以使用Scheduler.io作为开始。
英文:
Monix's default Scheduler delegates to the Scala default ExecutionContext, so if you were trying to avoid using that EC, you should define your own Scheduler instance that uses your custom EC. The Scheduler companion has a handful of apply methods defined for this purpose.
A Scheduler uses a ScheduledExecutorService for scheduling tasks, and an ExecutionContext for running the tasks. So if you pass your custom ExecutionContext to the Scheduler.apply, your tasks will run there.
A note about interop between Task and Future: a Task is lazy by nature. Constructing one does not run it. So when wrapping a Future-returning expression as a Task, the deferFuture takes the expression in call-by-name style, so that the Future will not actually be constructed and started until that Task is told to run.
Since you mentioned the Tasks contain IO (I'm assuming you mean in the "I/O" sense, not cats.effect.IO), it might be appropriate to set up a separate dedicated thread pool (ExecutionContext) for performing IO-bound operations, so those don't block your CPU-bound "compute" threads. See Scheduler.io as a starting point for that.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论