読者です 読者をやめる 読者になる 読者になる

きくらげ観察日記

好きなことを、適当に。

ScalaFX: TaskとService

JavaFXでは別スレッドで作業を行うためにTaskとServiceという2つのクラスを提供しています。
ScalaFXでも同名のクラスは用意されているのですが、そのコンストラクタの定義を見てみると

new Task(delegate: javafx.concurrent.Task[T])
new Service(delegate: javafx.concurrent.Service[T])

となっており、実質JavaFXのものを使うしかない状態です。

また、ScalaFX(JavaFX)ではUI部分の変更はメインとなるスレッド(アプリケーションスレッド)でしか実行できないため、Taskを実行する上では注意が必要です。

javafx.concurrent.Task

Taskは、1回きりの使い捨てとなるスレッドを生成するためのクラスです。抽象メソッドであるcall()をオーバーライドすることによって、そのメソッドが外部スレッドで行われるようになります。
ただし、call()が成功した際に実行されるsucceeded()や、失敗した場合に呼ばれるfailed()等はアプリケーションスレッドから実行されるので、UI部分の操作等はこのメソッドから行うとよいです。

また、アプリケーションスレッドと通信するための手段として、進捗状況を表すprogress, タスクの状態を表すstate, タスクの実行結果を表すvalue等のプロパティが用意されています。

Service

Serviceクラスは何度も実行される想定の動作のためのクラスです。
ユーザーがオーバーライドするのはcreateTask()メソッドで、このメソッドを呼ぶ度に次に実行すべき動作をTaskの形で返します。Service.restart()が呼ばれる度にこのcreateTask()が呼ばれ、生成されたTaskのcall()が別スレッドで呼ばれます。

サンプル

import scala.annotation.tailrec
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.geometry.{NodeOrientation, Insets}
import scalafx.scene.Scene
import scalafx.scene.control.{Button, Label, ProgressBar}
import scalafx.scene.input.MouseEvent
import scalafx.scene.layout.{HBox, VBox}
import scalafx.concurrent.Service
import javafx.concurrent.{Service => JService, Task => JTask}

class Counter extends JService[Unit] {
  override protected def createTask() = new JTask[Unit]() {
    override protected def call() {
      @tailrec def iterate(i: Int) {
        if (i >= 100 || isCancelled) {
          // break
        } else {
          Thread.sleep(100)
          updateProgress(i, 100)
          iterate(i + 1)
        }
      }
      iterate(0)
    }
  }
}
object TaskAndService extends JFXApp {
  val counter: Service[Unit] = new Counter()
  stage = new PrimaryStage {
    title = "task and service"
    scene = new Scene {
      root = new VBox {
        padding = Insets(10)
        spacing = 5
        children = Seq(
          new HBox {
            spacing = 5
            children = Seq(
              new Label("Progress: "),
              new ProgressBar {
                progress <== counter.progress
              }
            )
          },
          new HBox {
            nodeOrientation = NodeOrientation.RIGHT_TO_LEFT
            children = new Button {
              text = "Run!"
              onMouseClicked = (ev: MouseEvent) => {
                if (!counter.running())
                  counter.restart()
              }
            }
          }
        )
      }
    }
  }
}

実行例

f:id:cloudear8:20160227030847p:plain
「Run!」ボタンをクリックする毎にプログレスバーが初期化され、10秒間かけて100%まで進みます。

Scalaファンクショナルデザイン ―関数型プログラミングの設計と理解

Scalaファンクショナルデザイン ―関数型プログラミングの設計と理解

JavaFX & Java8プログラミング―Javaによる新しいGUIプログラミング入門

JavaFX & Java8プログラミング―Javaによる新しいGUIプログラミング入門