倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

はじめての幻獣 Griffon 研 (17) : コストのかかる計算を別スレッドで(実践編) edt / doLater / doOutside

今回は前回見た Griffon でのスレッド・プログラミングを「関数描画アプリケーション」に適用してみます(一覧)。 適用対象はグラフを描画する処理 Controller.paintGraph です(以下、FunctionPlotterController などを Controller などと書いてる箇所があります)。

もともとの paintGraph


もともとの paintGraph の実装はこんな感じでした:

import org.jfree.data.general.DatasetUtilities

class FunctionPlotterController {
    ...
    def paintGraph = { evt = null ->
        def func = new ScriptFunction2D(model.function)
        def dataset = DatasetUtilities.sampleFunction2D(func, model.from, model.to, model.samples, 'f(x)')
        view.coordinate.chart.plot.dataset = dataset
    }
}

この中でコストのかかる処理は・・・dataset の作成でしょうか。 まぁ、とりあえず EDT 外でできることは EDT 外でやりましょう。

準備


paintGraph の実装の前に、いくつか便利メソッドを定義しておきます。

  • Model#copyProperties()
  • Controller#createDataset()

Model#copyProperties() メソッド

まず Model のプロパティを一括して Map で取得するメソッドを定義します。 前回やったように model のプロパティへのアクセスは EDT 内で行い、それらの使用は EDT 外で行うので、使用するプロパティを一括して取得できると便利です。 このメソッドを copyProperties() として Model に実装しましょう:

class FunctionPlotterModel {
    ...
    def Map copyProperties(){
        return [function:function, samples:samples, from:from, to:to, min:min, max:max]
    }
}

使い方はこんな感じ:

def props = model.copyProperties()

Controller#createDataset() メソッド

次は Model のプロパティ(のコピー)から Dataset を作成するユーティリティ・メソッド createDataset() を Controller に定義します (static):

import org.jfree.data.general.DatasetUtilities

class FunctionPlotterController {
    ...
    static createDataset(Map props) {
        def func = new ScriptFunction2D(props.function)
        return DatasetUtilities.sampleFunction2D(func, props.from, props.to, props.samples, 'f(x)')
    }
}

この処理は model や view のプロパティにアクセスしないので、EDT 外で実行します。 ScriptFunction2D クラスは以前の記事参照。 これらの便利メソッドを使うと paintGraph は以下のようになります:

class FunctionPlotterController {
    ...
    def paintGraph = { evt = null ->
        def props = model.copyProperties()
        def dataset = createDataset(props)
        view.coordinate.chart.plot.dataset = dataset
    }
}

これを踏まえて以下でスレッド・プログラミング。

paintGraph その1


上記の paintGraph で EDT 外で行う処理は2行目の「Dataset の作成」。 これを前回の方法で実装すると下記のようになります:

class FunctionPlotterController {
    ...
    def paintGraph = { evt = null ->
        def props = model.copyProperties()    // model からプロパティを取得

        doOutside{
            def dataset = createDataset(props)    // コストのかかる計算

            doLater{
                view.coordinate.chart.plot.dataset = dataset    // view へ dataset をセット
            }
        }
    }
}

これで完了。 で、実行してみると、以前フリーズしていた時間の前半分くらいは確かにフリーズしなくなったけど、後ろ半分くらいはまだ相変わらずフリーズ。 うーむ、EDT 内で dataset をセットするのはよくないようで。

paintGraph その2


チャートに dataset を設定するのにもコストがかかるようなので、EDT 外で JFreeChart オブジェクトを作成して、dataset をセットした上で EDT 内で view の適切な部分にこの JFreeChart オブジェクトを埋め込むように変更してみましょう。

import org.jfree.chart.ChartFactory
import org.jfree.chart.plot.PlotOrientation as PO

class FunctionPlotterController {
    ...
    def paintGraph = { evt = null ->
        def props = model.copyProperties()

        doOutside{
            def dataset = createDataset(props)
            def chart = ChartFactory.createXYLineChart(null, 'x', 'y', dataset, PO.VERTICAL, false, false, false)
            chart.plot.rangeAxis.with{
                lowerBound = props.min
                upperBound = props.max
            }

            doLater{
                view.coordinate.chart = chart
            }
        }
    }
}
  • JFreeChart オブジェクトは ChartFactory クラスの static メソッドから直接作成します。
  • チャート自体を作り替えるので、y 軸の目盛りの設定 (chart.plot.rangeAxis.with{...}) も行います。
  • view.coordinate で参照されるコンポーネント(ChartPanel オブジェクト)に JFreeChart オブジェクトをセットします(EDT 内にて)。

これでいくらかはフリーズ時間が改善された気がします(ちゃんと測定するテクがないので雰囲気:-P)

paintGraph その3


上記の実装でそんなに問題はないかと思いますが、ちょっと気になるのは Charts プラグインを導入した際に作成した CoordinateChart.groovy がほとんど無意味になってしまったこと。 初期化の際には使われますが、いったんグラフを描画するとそこに施した設定が反映されなくなります。 paintGraph の処理内にそれらチャートの設定を書くことも可能ですが、View に関連する設定は別ファイルにしておいた方がいいでしょう。

ということで、以下では CoordinateChart.groovy の設定を読み込んで JFreeChart オブジェクトを作成するようにしてみましょう。 以前にやった『「GroovyでBuilder系の定義ファイルの外出しする」の別アプローチを思いついてみた』で見た方法を使ってます:

import com.thecoderscorner.groovychart.chart.ChartBuilder

class FunctionPlotterController {
    ...
    def paintGraph = { evt = null ->
        def props = model.copyProperties()

        doOutside{
            def data = createDataset(props)
            def chart = createChart(CoordinateChart.class)

            chart.plot.with{
                dataset = data
                rangeAxis.with{
                    lowerBound = props.min
                    upperBound = props.max
                }
            }

            doLater{
                view.coordinate.chart = chart
            }
        }
    }

    /** 指定された Script クラス(GroovyChart によるチャートの構築が記述されている)から JFreeChart オブジェクトを作成 */
    static createChart(Class chartScriptClass){
        def chartScript = chartScriptClass.newInstance()
        def builder = new ChartBuilder()
        chartScript.metaClass.methodMissing = { String name, args -> builder.invokeMethod(name, args) }
        return chartScript.run().chart
    }
}

これで完了。 うーむ、最後の方はスレッド・プログラミングと関係なくなってる気もするけど・・・まっ、いっか(笑)

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編