IntelliJ Platform Pluginの開発にて、Task.Backgroundableを使って重い処理をバックグラウンド実行してみた

以前、「ToolWindow上でボタンをクリックしたらラベルの値を更新する」をためしました。
IntelliJ Platform Pluginの開発にて、ApplicationManagerやToolWindowManagerを使って、Actionの中でToolWindow上のlabelを更新してみた - メモ的な思考的な

 
しかし、「ボタンをクリックした後、重い処理を実行し、その結果をもとにラベルを更新する」を行った場合、上記の方法だけではUIがフリーズしてしまいました。

そこで、UIのフリーズを避ける方法がないかを探したところ、 Task.Backgroundable を使えば良さそうでした。

ただ、残念ながら、 Task.Backgroundable については、IntelliJ Platform Pluginの公式ドキュメントに記載が見当たりませんでした (Background Tasks という項目は Will be available soon になっていたので、そのうち公開されるかもしれませんが)。

一方、JetBrainsの別のツール(MPS)にはそれっぽい使い方が記載されていました。
Running in the background | Progress indicators | MPS Documentation

 
そこでどんなふうに使えばよいか気になったことから、実際に Task.Backgroundable をためしてみたときのメモを残します。

 
目次

 

環境

 
なお、ソースコードは前回の記事の続きに追加していきます。

また、「ボタンをクリックした後、重い処理を実行し、その結果をもとにラベルを更新する」を確認するため、UIについては以前作成した UpdateInActionContent.kt に追加する形とします。

 

UIがフリーズしてしまう例

最初に、UIがフリーズしてしまう例として、「ボタンを押すと5秒ほど時間がかかった後にラベルを更新する」を実装します。

 

実装

まずはActionを用意します。この中で

  • 5秒待つ
  • ラベルを更新する

を実装します。

class HeavyActionWithoutBackground : AnAction() {
    override fun actionPerformed(e: AnActionEvent) {
        val project = e.getRequiredData(CommonDataKeys.PROJECT)

        // 5秒待つ
        Thread.sleep(5000)

        // ラベルを更新する
        ApplicationManager.getApplication().invokeLater {
            val toolWindow = ToolWindowManager.getInstance(project).getToolWindow("UpdateInActionToolWindow")
                ?: return@invokeLater

            val panelComponent = toolWindow.component.components.filterIsInstance<DialogPanel>().first()
            val labelComponent = panelComponent.components.filterIsInstance<JLabel>().find {
                it.name == "myLabel"
            }

            if (labelComponent != null) {
                labelComponent.text = "Update By Heavy Action"
            }
        }
    }
}

 
続いて、ToolWindow上にボタンを用意します。

button("Heavy Action Without Background", HeavyActionWithoutBackground())

 

動作確認

では動作確認をします。

Heavy Action Without Background ボタンをクリックするとActionが実行されます。ただ、実行中はUI (ここでは右上の設定ボタン) を操作しようとしてもフリーズしています。

5秒経過後にラベルテキストが書き換わるとUIフリーズが解除され、設定メニューが表示されます。

 

Task.Backgroundableを使って、UIのフリーズを回避する例

続いて、 Task.Backgroundable を使った実装を見ていきます。

 

実装

まずは、 Task.Backgroundable を継承したクラスを用意します。

run メソッドをオーバーライドしてラベルの更新処理を実装します。

また、 run メソッドの引数 indicator を使い

indicator.fraction = 0.1
indicator.text = "Heavy process start"

と書くことで、バックグラウンドでの進捗を表現できます。上記の実装では、ステータスバーに10%の進捗と Heavy process start という文言が表示されます。

 
ソースコード全体はこちら。

class UpdateTask(private val project: Project): Task.Backgroundable(project, "task start...") {
    override fun run(indicator: ProgressIndicator) {
        indicator.fraction = 0.1
        indicator.text = "Heavy process start"

        Thread.sleep(5000)

        indicator.fraction = 0.9
        indicator.text = "Heavy process end"

        ApplicationManager.getApplication().invokeLater {
            val toolWindow = ToolWindowManager.getInstance(project).getToolWindow("UpdateInActionToolWindow")
                ?: return@invokeLater

            val panelComponent = toolWindow.component.components.filterIsInstance<DialogPanel>().first()
            val labelComponent = panelComponent.components.filterIsInstance<JLabel>().find {
                it.name == "myLabel"
            }

            if (labelComponent != null) {
                labelComponent.text = "Update By Heavy Action With Background"
            }
        }
    }
}

 
続いて、Actionを実装します。

Task.Backgroundableインスタンスをキューに入れます。

class HeavyActionWithBackground : AnAction() {
    override fun actionPerformed(e: AnActionEvent) {
        val project = e.project ?: return
        UpdateTask(project).queue()
    }
}

 
あとはUIのボタンを実装すれば完成です。

button("Heavy Action With Background", HeavyActionWithBackground())

 

動作確認

では、実際に動かしてみます。

ボタンをクリックすると、ステータスバーに進捗が表示されました。また、UIはフリーズせず、設定ボタンのメニューも表示されます。

 

ソースコード

Githubにあげました。
https://github.com/thinkAmi-sandbox/hello_jetbrains_plugin

今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/hello_jetbrains_plugin/pull/20