« TableViewerのホントの利用方法 | トップページ | 「Eclipseプラグイン開発」記事一覧 »

2004.05.02

TableViewerの更新はスレッドに注意せよ

現在のEclipseの最新リリースバージョンは2.1.3であり,僕もそのバージョンを使っている。今までの記事はすべて2.1.3を対象にしたものである。しかし,もしかしたら「TableViewerのホントの利用方法」で取り上げた例について,古いバージョンのEclipseを使用していた場合に「動かないじゃん!」ってなった人がいるかもしれない。実は,「TableViewerのホントの利用方法」でのコーディング例の中で,非常に危険な部分が存在している。ContentProvider内でTableViewerにドメインモデルを反映している部分(employeeAddedメソッド内のTableViewerに対するaddメソッド呼び出し)で,例外が発生してしまう可能性があるのだ。

では,実際にわざと危険な状況を作ってみる。「TableViewerのホントの利用方法」の中で取り上げたDivisionContentProviderクラスのemployeeAddedメソッドを,以下のように書き換える。

  public void employeeAdded(final Employee employee) {
    if (viewer != null) {
      Thread thread = new Thread(new Runnable() {
        public void run() {
          viewer.add(employee);
        }
      });
      thread.start();
    }
  }

Threadクラスを使用して,TableViewerへのEmployeeオブジェクトの追加処理を,employeeAddedメソッドを実行しているスレッドとは別のスレッドで実行している。この変更により,setInputメソッドでTableViewerにセットされているDivisionオブジェクトのaddEmployeeメソッドを使ってEmployeeオブジェクトを追加した場合,以下のような例外が発生してしまう。

  org.eclipse.swt.SWTException: Invalid thread access
   at org.eclipse.swt.SWT.error(SWT.java:2330)
   at org.eclipse.swt.SWT.error(SWT.java:2260)
   at org.eclipse.swt.widgets.Widget.error(Widget.java:385)
   at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:315)
   at org.eclipse.swt.widgets.Table.getItemCount(Table.java:817)
   at org.eclipse.jface.viewers.TableViewer.indexForElement(TableViewer.java:330)
   at org.eclipse.jface.viewers.TableViewer.add(TableViewer.java:114)
   at org.eclipse.jface.viewers.TableViewer.add(TableViewer.java:133)
   at yoichiro.myPlugin.DivisionContentProvider$1.run(DivisionContentProvider.java:62)
   at java.lang.Thread.run(Thread.java:534)

上記の例では,明示的に新しいスレッドを起動しているが,実際にはDivisionオブジェクトがどのスレッドにより操作されるかはプラグインの作り方次第である。TableViewerへの操作はすなわちSWTのTableコンポーネントへの操作になるのだが,当然マルチスレッド化での同一リソース(この場合はTableコンポーネント)に対する複数スレッドからの操作について,何らかの安全策がなければならない

実はSWTにおいて,
  ・GUIのイベントディスパッチを行っているスレッド(Eclipse2.1.3ではメインスレッド)からしか操作ができない
というお約束事がある。この制限により,SWTコンポーネントに対する複数の処理の順序が保たれている。上記の例では,独自に生成したスレッド内からSWTコンポーネントに対して操作が行われたために,SWTコンポーネント内部のチェックに引っかかって例外が発生した,というわけなのである。

ではどうしたらいいかというと,SwingでもinvokeLaterメソッドが存在するように,SWTにおけるGUIのイベントディスパッチスレッドに対して,SWTコンポーネントへの処理の実行をお願いすることになる。GUIのイベントディスパッチスレッドは,SWTではDisplayオブジェクトがそれに該当するので,操作したいコンポーネントからDisplayオブジェクトを取り出して,それに対して処理の実行を依頼する。

  public void employeeAdded(final Employee employee) {
    if (viewer != null) {
      Control control = viewer.getControl();
      if ((control != null) && !(control.isDispose()) {
        control.getDisplay().syncExec(new Runnable() {
          public void run() {
            viewer.add(employee);
          }
        });
      }
    }
  }

まず,TableViewerオブジェクトからSWTコンポーネント(この場合Tableコンポーネント)のオブジェクトをgetControlメソッドを使って取得する。そして,その結果がnullではなく,しかもSWTコンポーネントが破棄されていないことを確認する(Eclipseの終了時やその他突発的なSWTコンポーネントの破棄に対処するため)。

そしてSWTコンポーネントからgetDisplayメソッドを使ってDisplayオブジェクトを取得し,それが持つsyncExecメソッドに(Javaのスレッド関連でおなじみの)Runnableインタフェースのオブジェクトを渡して,GUIのイベントディスパッチスレッドに処理を依頼している。上記の例では,TableViewerオブジェクトに対してEmployeeオブジェクトをaddメソッドにより追加する処理をrunメソッド内に実装している。これで,先のSWTコンポーネントのルールに則った操作が行われるようになった。

syncExecメソッドが呼び出されると,Displayオブジェクトは適当なタイミングで渡された処理を実行する(すぐに実行されるとは限らない)。syncExecメソッドの場合,渡した処理が完了するまでメソッドは復帰しない。それとは対照的に,処理の実行を依頼するだけで,渡した処理の実行完了を待たなくてもいい場合は,asyncExecメソッドを使用することもできる。

syncExecメソッドとasyncExecメソッド,どっちを使うかは開発者の自由。というか,行わせたい処理の内容に従って決定されるべきだろう。ちなみに,syncExecメソッドはプログラム次第でデッドロックの危険性が存在するので注意が必要である。

|

« TableViewerのホントの利用方法 | トップページ | 「Eclipseプラグイン開発」記事一覧 »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/12631/525396

この記事へのトラックバック一覧です: TableViewerの更新はスレッドに注意せよ:

« TableViewerのホントの利用方法 | トップページ | 「Eclipseプラグイン開発」記事一覧 »