目的
- デバッグ中あるいは問題発生時など、何らかのダイアログを表示したいことはあります
- でも、バックグラウンドスレッドの場合には単純にダイアログを表示することはできないです
- 実行中のスレッドが UI スレッドかどうかを判定して、ダイアログの表示の仕方を切り替えるユーティリティを作ってみました
UI スレッド判定
Eclipse で UI スレッド以外から、画面関係の操作をすると例外が発生します。
該当のチェック場所は org.eclipse.swt.widgets.Widget#checkWidget()
だと思います。
この処理を見ると、Widget オブジェクトが持っている Display オブジェクトとくくりついているスレッドと、現在のスレッドが一致しているかを確認しているようです。
これと同じ判定をすれば、現在のスレッドが UI スレッドかどうかの判定ができると思います。
ただ、該当のロジックでは Thread オブジェクトだけではなく、OSレベルのスレッドIDもチェックしているようです。
// 省略
protected void checkWidget () {
Display display = this.display;
if (display == null) error (SWT.ERROR_WIDGET_DISPOSED);
if (display.thread != Thread.currentThread ()) {
/*
* Bug in IBM JVM 1.6. For some reason, under
* conditions that are yet to be full understood,
* Thread.currentThread() is either returning null
* or a different instance from the one that was
* saved when the Display was created. This is
* possibly a JIT problem because modifying this
* method to print logging information when the
* error happens seems to fix the problem. The
* fix is to use operating system calls to verify
* that the current thread is not the Display thread.
*
* NOTE: Despite the fact that Thread.currentThread()
* is used in other places, the failure has not been
* observed in all places where it is called.
*/
if (display.threadId != OS.GetCurrentThreadId ()) {
error (SWT.ERROR_THREAD_INVALID_ACCESS);
}
}
if ((state & DISPOSED) != 0) error (SWT.ERROR_WIDGET_DISPOSED);
}
// 省略
コメントを見る限りだと、IBM の JavaVM 1.6の JIT のバグらしきものの対策のように思います。
残念ながら、OSレベルのスレッドIDを取得することはできないので、無視してしまいます。
ダイアログを表示するメソッド
そんなわけで、以下のようなメソッドを作りました。
public static void openMessageDialog(final String message) {
IWorkbench workbench = PlatformUI.getWorkbench();
Display display = workbench.getDisplay();
if (display == null || display.isDisposed()) {
// Display がない
return;
}
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if (window == null) {
// IWorkbenchWindow がない
return;
}
final Shell shell = window.getShell();
if (display.getThread() == Thread.currentThread()) {
// UI スレッドの場合
MessageDialog.openInformation(shell, "Message", message);
} else {
// バックグラウンドスレッドの場合
display.syncExec(new Runnable() {
@Override
public void run() {
MessageDialog.openInformation(shell, "Message", message);
}
});
}
}
ダイアログの出し方の詳細は好みがあると思いますし、エラーダイアログ(ErrorDialog)を出したいケースもあるかと思います。
また上記では同期(syncExec)でダイアログを表示していますが、ケースによっては非同期(asyncExec)で表示したいこともあるかもしれません。
実装の詳細についても Optinal が使いたいとか、ラムダが使いたいとかありますが、今回は Java7 以前でも動くように匿名クラスを使ってみました。
とりあえず、UIスレッドでもバックグラウンドスレッドからでも簡単にメッセージダイアログが表示できるようになりました。