続いて、ウィジェットへのイベントに対し、ロジックを呼び出すリスナーを登録します。
openメソッドでメニューを作成した直後に、各メニューに対して呼び出すべき処理を登録します。
// 開くメニュー |
メニューへの処理の登録は、各メニューに対しaddSelectionListenerメソッドを使用してリスナーを追加することで実現できます。
SelectionAdapterクラスは、メニューが持つaddSelectionListenerメソッドの引数ISelectionインターフェイスを空実装するクラスです。SelectionAdapterのwidgetSelectedメソッドを、無名クラスを用いてオーバーライドすることで、ISelectionインターフェイスのすべてのメソッドを実装しなくてもよくなります。
createTableメソッド内で、テーブルに対し、メニューと同様にリスナーを登録します。
table.addSelectionListener(new SelectionAdapter() { |
ここまでのソースファイルはここかから入手できます。(ファイル名、クラス名を変えています)
以上のコーディングを終了したら、プログラムを実行してみてください。適当なCSVファイルを用意し、読み込んでみましょう。
これでCSVViewerの機能としては一応完成です。ここまでのソースは、ここからダウンロードしてください。
しかし、実はこのアプリケーションには問題があります。それは、行数の多いCSVファイルを読み込んだときに、ファイル読み込み中はアプリケーションが停止してしまうという問題です。読み込み状況が分からないとともに、ウィンドウの移動や最小化などが行えなくなってしまいます。
次では、この問題を解決すべく、アプリケーションの修正を行います。
正確には、アプリケーションは停止しているわけではありません。loadFileメソッド実行中に、GUIへの変更を描画する処理が呼び出されなくなるため、再描画やウィンドウの状態変化(イベント)などへの処理が遅れてしまうのが原因です。
mainメソッド内のループでDisplayオブジェクトのreadAndDispatchメソッドを呼び出していますが、実はこのメソッドを呼び出すことで描画やイベントの処理が行われます。SWTではDisplayオブジェクトを生成し、ループ内でreadAndDispatchメソッドを実行するスレッドをUIスレッドと呼びます。
このUIスレッドが長時間かかる処理を呼び出すと、アプリケーションが停止しているように見えてしまいます。
そこでこの状況を解決するために、別スレッド上でファイルの読み込み処理を行わせるように修正します。
別スレッドで読み込み処理を行うための、Runnableを実装するクラスを作成します。
読み込み処理では、CSVViewerがインスタンス変数として持つウィジェットを多数参照する必要があります。Runnableを実装するクラスをCSVViewerとは別のソースファイルとして作成することもできますが、今回はプログラムを簡易にするために、CSVViewerのインナークラスとして作成します。
class CSVLoader implements Runnable { |
loadBegin、loadFile、loadEndを呼び出していた部分を、別スレッドを生成して実行するように変更します。
// 開くメニュー |
loadBegin、loadFile、loadEndメソッドをCSVLoaderに移動します。また、これらのメソッドからのみ使用するインスタンス変数beginTimeもCSVLoaderに移動します。そして、runメソッド内でこれらのメソッドを呼び出すようにします。
CSVLoader部分のコードは、以下のようになります。
class CSVLoader implements Runnable { |
以上でファイルの読み込み処理を別スレッドに委譲するコードになりました。
さて、この状態でプログラムを実行すると、どうなるでしょうか。実は、[開く]メニューを選択不可にするところで、以下のようなSWTExceptionが発生します。
org.eclipse.swt.SWTException: Invalid thread access
|
この例外は、SWTのウィジェットを操作する処理が、上記で述べたUIスレッド以外から実行されたために発生します(UIスレッドにのみ実行が許されている)。SWTではこのような制限を設けることによって、複数スレッドからのGUI操作に対してシンプルでかつ安全な動作を提供しています。詳細については、Eclipseのヘルプの[Platform プラグイン・デベロッパー・ガイド]->[プログラマーズ・ガイド]->[Standard Widget Toolkit]->[スレッド化の問題]を参照してください。
SWTでは、このように別スレッドからSWTのウィジェットを操作するときには、DisplayクラスのsyncExec、asyncExecメソッドを使用して、UIスレッドに処理を委譲します。
仕組みが理解できたところで、コードを修正していきましょう。
修正対象は、loadBegin、loadFile、loadEndメソッドになりますが、まずはユーティリティメソッドを追加します。マルチスレッド環境下でdisplayが破棄されることも考慮し(例えば処理中にウィンドウが閉じられたなど)、以下のユーティリティメソッドをCVSLoaderに追加します。
private boolean checkAsyncExec(Runnable r) { |
SWTのウィジェットを操作する個所を、checkAsyncExecメソッドを使ってUIスレッドに実行させるように修正します。
private void loadBegin() { |
loadBeginと同様に、checkAsyncExecメソッドを使用します。whileループの中のみ、以下にコードを示します。
private void loadFile() { |
Runnableのrunメソッド内でloadFileメソッドのローカル変数datasを参照するため、datasをfinal宣言しています。また、display、tableのどちらかが破棄されていた場合には処理を続けても無意味なので、メソッドを抜けるようにしています。
loadBeginメソッドと同様に、checkAsyncExecメソッドを使用します。
private void loadEnd() { |
メニュー、ステータスバーが破棄されていた場合には、処理を続けられないのでメソッドを抜けるようにしています。
以上で完成です。ここまでのソースファイルはここから入手できます(ファイル名、クラス名を変えています)。
CSVViewerを実行し、CSVファイルの読み込み中に、テーブルのスクロールバーの移動、ウィンドウの最小化や移動を行ってもアプリケーションが素早く応答することを確認してください。
さて、ここまでのCSVViewerではアプリケーションの応答速度は向上しましたが、その半面、ファイルの読み込みにより時間がかかるようになっています。CSVViewerの最後の修正として、チューニングを行います。
読み込むファイルの大きさや、ディスクやCPUなどの実行環境の性能にもよりますが、筆者の環境では1万行のレコードを読み込むのに、スレッド化していないもので7.0秒、スレッド化したもので11.7秒と、スレッド化によって約1.7倍の時間がかかっています。
asyncExecメソッド1回の呼び出しにつき、実行オブジェクトの生成、Display内のキューへの積み上げが行われます。そしてUIスレッドによる実行オブジェクトのキューからの取得、実行が行われます。その間には、OSからのイベントの監視なども行われます。
当然これらの処理に対するオーバーヘッドが存在します。そして呼び出し回数が多くなると、そのオーバーヘッドが無視できない大きさになってしまいます。
通常、このような呼び出し回数に関する問題は、バッファリングにより呼び出し回数を減らすことで対応することができます。そこで、ファイルからの読み込み1行ごとに呼び出しているasyncExecメソッドを、100行ごとに呼び出すように修正します。
ArrayListに読み込んだデータを一時的に格納しておくことにより、バッファリングを実現します。
private void loadFile() { |
完成したソースファイルはここから入手できます(ファイル名、クラス名を変えています)。
実行し効果を確認したところ、筆者の環境では7.9秒とスレッド化とチューニングを施していないものと比べて約1.1倍までオーバーヘッドを縮めることができました。
このチューニングの注意点としては、asyncExecメソッドに委譲する処理の中で長時間かかる処理を実行してしまうと、前節で作成したCSVViewerと同じようにUIスレッドが占有されてしまうことです。最適なバッファサイズについては環境に依存しますし、また今回の記事の趣旨からは外れますので、CSVViewerはこれで完成とします。
本稿では、SWTについての解説と、Eclipseを用いたSWTアプリケーションの作成方法を紹介しました。また、SWTアプリケーションでUIスレッドとは別のスレッドを使用する方法と、SWTの重要な仕組みであるasyncExecメソッドに特化した簡単なチューニングも行いました。
SWTを使用すれば、GUIのコンポーネントを組み合わせて、高度なアプリケーションを簡単に組み上げることができます。それは、EclipseがSWTで記述されていることからもお分かりいただけると思います。
本稿が参考となり、快適なクライアントアプリケーションやEclipseプラグインを構築するためのきっかけとなれば幸いです。
次回の記事では、今回のSWTの内容を踏まえ、Eclipseのプラグインを開発します。お楽しみに。
Copyright © ITmedia, Inc. All Rights Reserved.