我有一个SBT任务,它使用taskDyn从配置文本文件中为多个CPU密集型计算动态创建任务。默认情况下,计算任务的计算并行运行,这很好,但现在我需要限制并行运行的数量(否则本机库OpenBLAS崩溃)。 我尝试使用Tag.CPU来强制执行此限制(完整示例here):
import sbt._
import Keys._
import Def.Initialize
// ...
lazy val runAllValidations = taskKey[Seq[Unit]]("Runs all standard validations")
lazy val validations = settingKey[Seq[String]]("All standard validations")
def validationTaskFor(arguments: String): Initialize[Task[Unit]] =
(runMain in Compile).toTask(s" com.test.foo.validation.RunValidation $arguments") tag(Tags.CPU)
def validationTasksFor(arguments: Seq[String]): Initialize[Task[Seq[Unit]]] = Def.taskDyn {
arguments.map(validationTaskFor).joinWith(_.join)
}
validations := {
val fromFile = IO.read(file("validation_configs.txt"))
fromFile.split("\n").map(_.trim).toList
}
runAllValidations := Def.taskDyn { validationTasksFor(validations.value) }.value
concurrentRestrictions in Global := Seq(
Tags.limit(Tags.CPU, 2)
)
不幸的是,我无法使taskDyn和Tags的组合工作,所有计算都立即开始:
Started with foo
Started with bla
Started with hoorray
Started with yeeha
Started with yeah
Finished with foo
Finished with hoorray
Finished with bla
Finished with yeeha
Finished with yeah
这种组合是否已知不起作用或我是否持有错误?
谢谢!
答案 0 :(得分:5)
你是Any =>的受害者Scala中的单位转换。你正在做的是在一个任务中计算一些超级复杂的东西,然后通过将它转换为()来忽略它,并让scala忽略它。
以下是一些使用较低级Initialize
API折叠任务的代码(我希望将来更直接地公开)
我的build.sbt:
def dummyTaskGen(name: String): Def.Initialize[Task[Unit]] = Def.task {
System.err.println(s"Started ${name}")
Thread.sleep(1000L*6)
System.err.println(s"Finished ${name}")
}
lazy val validations = taskKey[Unit]("Run everything, one at a time.")
lazy val names = Seq("foo", "bar", "baz", "biz", "buzz", "bunk")
lazy val allRuns: Def.Initialize[Task[Unit]] = Def.settingDyn {
val zero: Def.Initialize[Seq[Task[Unit]]] = Def.setting { Seq(task(())) }
names.map(dummyTaskGen).foldLeft(zero) { (acc, current) =>
acc.zipWith(current) { case (taskSeq, task) =>
taskSeq :+ task.tag(Tags.CPU)
}
} apply { tasks: Seq[Task[Unit]] =>
tasks.join map { seq => () /* Ignore the sequence of unit returned */ }
}
}
validations := allRuns.value
concurrentRestrictions in Global ++= Seq(
Tags.limit(Tags.CPU, 2)
)
在命令行上:
> validations
Started bar
Started buzz
Finished buzz
Finished bar
Started biz
Started bunk
Finished biz
Finished bunk
Started baz
Started foo
Finished baz
Finished foo
[success] Total time: 18 s, completed Jul 7, 2014 2:11:43 PM
让我们深入研究我们在这里使用的方法:
首先,关键是你将zipWith
一起完成任务的整个初始化。
例如,在您的项目中,您有一种机制来生成名为validationTaskFor
的标记任务。这将返回Def.Intiailize[Task[Unit]]
,其中任务被适当地调整。
因此,现在您有一系列字符串,您可以将其转换为一系列初始化。从这里开始,我们需要合并所有Initialization
外层,以获得Task[_]
层中丰富多彩的优点。
请参阅:http://www.scala-sbt.org/0.13.5/api/index.html#sbt.Init $ Initialize for initialize。
注意:强> MOSTLY,初始化隐藏在宏后面(如
Def.task
,Def.setting
,Def.inputTask
,:=
和朋友)。但是,什么时候 使用动态任务生成,暴露的API并不完整 足够了,你点击scala隐含的那些区域之一 推理+我们的宏导致编译的东西是荒谬的 自然(即丢弃生成的所有Task [_]实例和 return()没有充分理由)。
现在,关于功能的内容:
Def.settingDyn
需要Initialize[T]
。在我们的例子中,T
是Task[Unit]
。所以我们知道我们需要将下面的表达式放到Initialize[Task[Unit]
中,所以让我们适当地键入allRuns
值以获得更好的编译器错误消息。
def allRuns: Def.Initialize[Task[Unit]] = Def.settingDyn { ... }
Def.Initialize
是一个应用程序,我们将有一系列这些。这意味着折叠,所以我们应该为折叠创建zero
。在这种情况下,当我们将所有任务合并在一起时,我们将返回一个Initialize[Seq[Task[Unit]]
,其中所有任务都在里面一个Initialize
容器中,我们可以在其中进行交互他们在一起。 (这类似于确保在我们尝试使用它们之前从State正确初始化/构造所有任务)。
val zero: Def.Initialize[Seq[Task[Unit]]] = Def.setting { Seq(task( () )) }
现在,我们将Seq[String]
转换为Seq[Initialize[Task[Unit]]
names.map(dummyTaskGen)`
我们折叠这个序列:
names.map(dummyTaskGen).foldLeft(zero) { (acc, current) => ... }
我们定义如何将Initialize[Seq[Task[Unit]]
与`Initialize [Task [Unit]]连接起来,即我们将新任务添加到现有的任务列表中:
acc.zipWith(current) { case (taskSeq, task) =>
taskSeq :+ task.tag(Tags.CPU)
}
我们现在有一个Initialize[Seq[Task[Unit]]
我们将所有这些加在一起:
tasks.join /* Task[Seq[Unit]] */
我们忽略Seq [Unit]结果,将其转换为Unit
tasks.join.map { ignore => () }
你应该做三件事:
Def.task
或Def.taskDyn
宏中公开机制的故障单,以直接向任务添加标记,从而删除需要您去的两件事中的一个降至API的这个级别。Initialize[Seq[Initialize[Task[T]]]]
应该可以在没有喧闹声的情况下加入,因为每次有人想要它都是相同的脏代码。