频繁调用多线程Swing程序中的setText()

时间:2010-11-09 21:47:36

标签: java multithreading swing

我有一个Swing程序,在非Swing线程中不断完成工作。它通常需要更新JTextPane - 通常每秒更新一次。我意识到setText()需要在事件调度线程中从后面调用,但我无法弄清楚如何顺利实现这一点。

以下最小完整示例尽可能接近我使用PipedInputStream / PipedOutputStream对,但这似乎只是每秒更新一次屏幕。我不确定这花了多长时间。

import java.awt.event.*;
import javax.swing.*;
import java.io.*;

public class TextTest extends JFrame {
    private JTextPane out = new JTextPane();
    private PipedInputStream pIn = new PipedInputStream();
    private PrintWriter pOut;

    public TextTest() {
        try {
            pOut = new PrintWriter(new PipedOutputStream(pIn));
        }
        catch (IOException e) {System.err.println("can't init stream");}

        add(new JScrollPane(out));
        setSize(500, 300);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);

        // Start a loop to print to the stream continuously
        new Thread() {
            public void run() {
                for (int i = 0; true; i++) {
                    pOut.println(i);
                }
            }
        }.start();

        // Start a timer to display the text in the stream every 10 ms
        new Timer(10, new ActionListener() {
            public void actionPerformed (ActionEvent evt) {
                try {
                    if (pIn.available() > 0) {
                        byte[] buffer = new byte[pIn.available()];
                        pIn.read(buffer);
                        out.setText(out.getText() + new String(buffer));
                    }
                }
                catch (IOException e) {System.err.println("can't read stream");}
            }
        }).start();
    }

    public static void main(String[] args) {
        new TextTest();
    }
}

我是否实施了这个错误?关于如何从EDT外部不断更新JTextPane我是否完全错误?

4 个答案:

答案 0 :(得分:4)

setText()“方法是线程安全的,但大多数Swing方法都不是。请参阅How to Use Threads以获取更多信息。”

附录:作为参考,这里有一些other approaches来更新EDT。需要注意的另一件事是javax.swing.Timer的动作事件处理程序在EDT上执行。这是我的变化:

import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import javax.swing.text.DefaultCaret;

public class TextTest extends JFrame {

    private JTextArea out = new JTextArea();
    private PipedInputStream pIn = new PipedInputStream();
    private PrintWriter pOut;

    public TextTest() {
        try {
            pOut = new PrintWriter(new PipedOutputStream(pIn));
        } catch (IOException e) {
            System.err.println("can't init stream");
        }

        DefaultCaret caret = (DefaultCaret) out.getCaret();
        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);

        add(new JScrollPane(out));
        setSize(300, 500);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);

        // Start a loop to print to the stream continuously
        new Thread() {

            public void run() {
                for (int i = 0; true; i++) {
                    pOut.println(i);
                }
            }
        }.start();

        // Start a timer to display the text in the stream every 10 ms
        new Timer(10, new ActionListener() {

            public void actionPerformed(ActionEvent evt) {
                try {
                    out.append(String.valueOf((char) pIn.read()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static void main(String[] args) {
        new TextTest();
    }
}

答案 1 :(得分:2)

  

但这似乎只是每秒钟更新一次屏幕。我不确定这花了多长时间。

System.out.println(pIn.available());

我将上述语句添加到Timer的actionPerformed代码中。在缓冲区达到1024字节之前没有任何反应。所以我猜你需要改变缓冲区大小。

此外,您不应该使用setText()。每次进行更改时重新创建文档都是低效的。

您可以使用:

out.replaceSelection(new String(buffer) );

或者更常见的方法是使用:

Document doc = textPane.getDocument();
doc.insertString("...", doc.getLength(), null);

不要认为insertString()方法是线程安全的,但是replaceSelection()方法是。

编辑:

尝试在输入流中使用缓冲区大小为10并刷新输出流并且它没有任何区别,所以我想我不理解管道流。

答案 2 :(得分:2)

你需要刷新printWriter的输出,并且我建议在你的线程中暂停一下,因为它有一个紧密的for循环,让更新线程偶尔启动。

 pOut.println(i); 
 pOut.flush();
 try {
      sleep(10);
 } catch (InterruptedException e) {
 }

这将使流程更顺畅。

答案 3 :(得分:0)

并发和Swing的正确教程链接看起来就在这里:Lesson: Concurrency in Swing

@camickr:setText没有创建新文档,它有效地做到了这一点:

doc.replace(0, doc.getLength(), s, null);

或者这个:

doc.remove(0, doc.getLength());
doc.insertString(0, s, null);

我并没有声称它效率很高......

setText

}的另一件事是导致revalidate()repaint()被发出(setDocument会这样做)。在调用setText之后添加这两个调用可能是值得的。