Frequent calls to setText() in multithreaded Swing program
I have a Swing program where work is continuously being done in a non-Swing thread. It often needs to update a JTextPane -- frequently many times per second. I realize that setText() needs to be called from back inside the event-dispatching thread, but I cant figure out how to make this happen smoothly.
The following minimal complete example is as close as I've been able to get it, using a PipedInputStream/PipedOutputStream pair, but this 开发者_开发知识库only seems to update the screen once every second or so. I'm not sure what's taking so long.
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();
}
}
Am I implementing this wrong? Do I just have totally the wrong idea about how to continuously update a JTextPane from outside the EDT?
The setText()
"method is thread safe, although most Swing methods are not. Please see How to Use Threads for more information."
Addendum: For reference, here's some other approaches to updating on the EDT. Another thing to note is that the action event handler for javax.swing.Timer
executes on the EDT. Here's my variation:
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();
}
}
but this only seems to update the screen once every second or so. I'm not sure what's taking so long.
System.out.println(pIn.available());
I added the above statement to the actionPerformed code of the Timer. Nothing happens until the buffer reaches 1024 bytes. So somehow I guess you need to change the buffer size.
Also, you should not be using setText(). It is inefficient to recreate the Document every time you make a change.
You could use:
out.replaceSelection(new String(buffer) );
Or the more common approach is to use:
Document doc = textPane.getDocument();
doc.insertString("...", doc.getLength(), null);
Don't think the insertString() method is thread safe, but the replaceSelection() method is.
Edit:
Just tried playing with a buffer size of 10 in the input stream and flushing the ouput stream and it didn't make any difference, so I guess I don't understand piped streams.
you need to flush the output of your printWriter and i'd suggest a small pause in your thread given its a tight for loop to let the update thread kick in once in a while.
pOut.println(i);
pOut.flush();
try {
sleep(10);
} catch (InterruptedException e) {
}
This will give a smoother flow.
The proper tutorial link for concurrency and Swing looks like it's here: Lesson: Concurrency in Swing
@camickr: setText
does not create a new document, it effectively does either this:
doc.replace(0, doc.getLength(), s, null);
or this:
doc.remove(0, doc.getLength());
doc.insertString(0, s, null);
I'm not claiming that it's efficient however...
Another thing that setText
does not do is to cause revalidate()
and repaint()
to be issued (setDocument
does, however). It's probably worthwhile to add those two calls after the call to setText
.
精彩评论