- PR -

JTextAreaで表示が遅れる

1
投稿者投稿内容
aa
ぬし
会議室デビュー日: 2004/01/08
投稿数: 299
投稿日時: 2005-05-10 21:01
Processを用いて外部コマンドを実行し、その出力結果(約100行)をJTextAreaに表示しようとしているのですが、プロセスが終わるまで表示がされずに、プロセスが終わるとまとめて表示されます。
なぜこのような現象が起き、また回避策はどうすればよいのか教えてください。

実際のコードではありませんが、イメージとしては次のような感じです。
コード:
Process実行開始

while (Processが終わるまで) {
    Processから1行取得し、String型の変数messageに入れる。

  JTextAreaにappend(message);
  System.out.println(message);
}


標準出力の方は、普通にプロセスの実行に合わせて1行づつだらだらと100行ほど表示されます。
そしてプロセスが終了すると、JTextAreaに100行どんとまとめて表示されます。

JTextAreaの方にもSystem.out.printlnと同じように1行づつだらだらと表示させたいのです。
takamaro
大ベテラン
会議室デビュー日: 2004/10/12
投稿数: 100
投稿日時: 2005-05-10 21:16
もしかするとイベントディスパッチスレッドで処理してるのでは?
a-san
ベテラン
会議室デビュー日: 2004/06/01
投稿数: 53
投稿日時: 2005-05-11 00:21
別スレッドでストリームを吐き出してやらなければならないので、
予想以上に難しいです。以下の場所のプログラムを参考にして作ってみました。
http://forum.java.sun.com/thread.jspa?threadID=56352&messageID=142267
コード:
// http://forum.java.sun.com/thread.jspa?threadID=56352&messageID=142267
//Multi-thread this. Here's an example of how I dealt with this exact problem:
import java.io.*;
import javax.swing.*;

// Execute the process
class TestProcess {
    public static void main(String[] args) {
        new TestProcess("java -h");
    }
    JTextArea textarea;
    TestProcess(String command) {
        JFrame frame = new JFrame();
        textarea = new JTextArea();
        frame.getContentPane().add(new JScrollPane(textarea));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(640, 800);
        frame.setVisible(true);

        Process deployProcess;
        try {
            deployProcess = Runtime.getRuntime().exec(command);
        }
        catch (java.io.IOException e) {
            return;
        }
        (new Thread(new TextVacuum(deployProcess.getInputStream()))).start();
        (new Thread(new TextVacuum(deployProcess.getErrorStream()))).start();
        try {
            deployProcess.waitFor();
        }
        catch (InterruptedException e){}
    }
    private class TextVacuum implements Runnable {
        public TextVacuum(java.io.InputStream inStream) {
            myStream = new java.io.BufferedReader(new
                java.io.InputStreamReader(inStream));
        }

        public void run() {
            try {
                char[] cbuf = new char[4096];
                int numRead = myStream.read(cbuf);
                while (numRead != -1) {
                    //addText(new String(cbuf, 0, numRead));
                    textarea.append(new String(cbuf, 0, numRead));
                    numRead = myStream.read(cbuf);
                }
            }
            catch (java.io.IOException e) {
                System.err.println("ERROR reading from process stream of:"+ e);
            }
            finally {
                try {
                    myStream.close();
                }
                catch (java.io.IOException e) {}
                myStream = null;
            }
        }

        private java.io.BufferedReader myStream;
    }
}

takamaro
大ベテラン
会議室デビュー日: 2004/10/12
投稿数: 100
投稿日時: 2005-05-11 01:33
イベントディスパッチスレッドでの操作が原因だとすれば大凡a-sanの方法で
宜しいのではないかと思います(なぜBufferedReaderまで昇華させておいて
read()なのかは多少疑問ではありますが、、私の見識不足!?)
1つ気になった点は、参照のソースはイベントディスパッチスレッドからの
呼び出しを前提としていない(と思われる)ので、、、
java.lang.Process#watitFor()
を用いてますが、これをイベントディスパッチスレッド上で行うと元の木阿弥。

>予想以上に難しいです。

でも、まだ今回のようにjavax.swing.JTextArea#append()のようにスレッド
セイフなメソッドだったから手間は掛からない方です。
もし非スレッドセイフな描画処理だったら別途呼び出したスレッド上で、また
別途新たなRunnableをinvokeAndWait()にでも放り投げなきゃならない訳でw
びしばし
大ベテラン
会議室デビュー日: 2002/03/13
投稿数: 181
投稿日時: 2005-05-11 10:28
API ドキュメントによると Reader#read(char[]) と read(char[], int, int) は
引用:

配列に文字を読み込みます。このメソッドは、文字が読み込まれるか、入出力エラーが発生するか、あるいはストリームの終わりに達するまでブロックします。


とあります。
今回バッファサイズが 4096ですが「java -h」を手元の JDK 1.5.0_03 で実行すると出力が 4096バイトもありませんでした。ですから「ストリームの終わりに達する == プロセスが終了する」までブロックされてしまったのではないかと思います。

以上、的はずれでしたらごめんなさい。
シュン
ぬし
会議室デビュー日: 2004/01/06
投稿数: 328
お住まい・勤務地: 東京都
投稿日時: 2005-05-11 11:24
そんなに長いコードにはならないと思いますよ。こんな感じでどうですか。
javax.swing.SwingUtilities#invokeLater()は、Swingアプリを作成する
際には習得必須のメソッドかと思います^^

コード:


Process生成;

new Thread(){
public void run(){
while (Processが終わるまで) {
javax.swing.SwingUtilities.invokeLater(new Runnable(){
pubilc void run(){
JTextAreaにappend(message);
}
});
}
}
}.start();





…イベントディスパッチスレッドの本来の使い道からは逸れているかも^^;

[ メッセージ編集済み 編集者: シュン 編集日時 2005-05-11 11:32 ]
aa
ぬし
会議室デビュー日: 2004/01/08
投稿数: 299
投稿日時: 2005-05-11 21:22
みなさん。ありがとうございます。
イベントディスパッチというのは知りませんでした。
これを基にいろいろと調べてみてなんとか思った通りになりました。

以下、ご参考まで。

SwingUtilities.invokeAndWait()、SwingUtilities.invokeLater() あたりを使えば解決するかと思ったのですがそう簡単にいきませんで、結論から言えば全く関係ありませんでした。
JButtonのActionListenerでSwingUtilities.invokeLater()を使ってというのは、Swingの表示が遅れる(いつ終わるか分からないという)状態で、ある処理が終わった後にJButtonをイネーブルにするように予約するとかいうような使い方です。

ポイントとしてはJButtonのActionListenerから別スレッドを起動して重たい処理はそちらでやってもらい、とっととActionListenerから抜けて自分自身のスレッドをフリーな状態にすることでした。
invokeLater()から重たい処理を投げても自分自身と同じスレッドでは無意味でした。

コード(抜粋)は次の通りです。
コード:
jButton.addActionListener(new java.awt.event.ActionListener() { 
	public void actionPerformed(java.awt.event.ActionEvent e) {
	    CommandClient client = CommandClient.getInstance();
	    Command command= client.getCommand(CommandConstants.OMOTEEE_SYORI);
	    Thread thread = new Thread(new ExecuteCommand(command));
	    thread.start();
	}
});


あと、参考になったサイトです。
http://www.hcn.zaq.ne.jp/no-ji/javamemo/javax_swing_SwingUtilities.html
http://www.dxjk.com/java/swing/invokelater.html
1

スキルアップ/キャリアアップ(JOB@IT)