Java 在多线程Swing程序中频繁调用setText()
我有一个Swing程序,其中工作是在非Swing线程中连续进行的。它经常需要更新JTextPane—经常是每秒多次。我意识到setText需要从事件调度线程的内部调用,但我不知道如何使其顺利进行 下面的最小完整示例与我所能得到的最接近,使用了PipedInputStream/PipedOutputStream对,但这似乎只是每秒左右更新一次屏幕。我不知道怎么花了这么长时间Java 在多线程Swing程序中频繁调用setText(),java,multithreading,swing,Java,Multithreading,Swing,我有一个Swing程序,其中工作是在非Swing线程中连续进行的。它经常需要更新JTextPane—经常是每秒多次。我意识到setText需要从事件调度线程的内部调用,但我不知道如何使其顺利进行 下面的最小完整示例与我所能得到的最接近,使用了PipedInputStream/PipedOutputStream对,但这似乎只是每秒左右更新一次屏幕。我不知道怎么花了这么长时间 import java.awt.event.*; import javax.swing.*; import java.io.
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,我是否完全没有正确的想法?该方法是线程安全的,尽管大多数Swing方法不是。有关更多信息,请参阅
附录:为了参考,这里有一些关于EDT的更新。另一件需要注意的事情是,的操作事件处理程序在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();
}
}
该方法是线程安全的,尽管大多数Swing方法不是。有关更多信息,请参阅
附录:为了参考,这里有一些关于EDT的更新。另一件需要注意的事情是,的操作事件处理程序在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();
}
}
但这似乎只是每秒钟左右更新一次屏幕。我不知道怎么花了这么长时间
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();
}
}
我将上述语句添加到计时器的actionPerformed代码中。在缓冲区达到1024字节之前,不会发生任何事情。所以我想你需要改变缓冲区的大小
此外,您不应该使用setText。每次进行更改时重新创建文档效率很低
您可以使用:
out.replaceSelection(new String(buffer) );
或者更常见的方法是使用:
Document doc = textPane.getDocument();
doc.insertString("...", doc.getLength(), null);
不要认为insertString方法是线程安全的,但replaceSelection方法是
编辑:
只是尝试在输入流中使用大小为10的缓冲区并刷新输出流,但没有任何区别,所以我想我不理解管道流
但这似乎只是每秒钟左右更新一次屏幕。我不知道怎么花了这么长时间
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();
}
}
我将上述语句添加到计时器的actionPerformed代码中。在缓冲区达到1024字节之前,不会发生任何事情。所以我想你需要改变缓冲区的大小
此外,您不应该使用setText。每次进行更改时重新创建文档效率很低
您可以使用:
out.replaceSelection(new String(buffer) );
或者更常见的方法是使用:
Document doc = textPane.getDocument();
doc.insertString("...", doc.getLength(), null);
不要认为insertString方法是线程安全的,但replaceSelection方法是
编辑:
只是尝试在输入流中使用大小为10的缓冲区并刷新输出流,但没有任何区别,因此我想我不理解管道流。有关并发性和Swing的正确教程链接如下所示: @camickr:setText不会创建新文档,它有效地执行以下操作之一:
doc.replace(0, doc.getLength(), s, null);
或者这个:
doc.remove(0, doc.getLength());
doc.insertString(0, s, null);
我并不是说它很有效,但是
但是,setText不做的另一件事是使setDocument发出重新验证和重新绘制。在调用setText之后添加这两个调用可能是值得的。有关并发性和Swing的正确教程链接如下所示: @camickr:setText不会创建新文档,它有效地执行以下操作之一:
doc.replace(0, doc.getLength(), s, null);
或者这个:
doc.remove(0, doc.getLength());
doc.insertString(0, s, null);
我并不是说它很有效,但是
但是,setText不做的另一件事是使setDocument发出重新验证和重新绘制。在调用setText之后添加这两个调用可能是值得的。您需要刷新printWriter的输出,我建议您的线程稍微暂停一下,因为它是一个紧密的for循环,让更新线程偶尔启动一次
pOut.println(i);
pOut.flush();
try {
sleep(10);
} catch (InterruptedException e) {
}
这将提供一个更平滑的流程。您需要刷新printWriter的输出,我建议您的线程稍微暂停一下,因为它是一个紧密的for循环,让更新线程偶尔启动一次
pOut.println(i);
pOut.flush();
try {
sleep(10);
} catch (InterruptedException e) {
}
这将提供一个更平滑的流程。@jjnguy:我不知道为什么API链接是404 oracled,但是版本7已经放弃了线程安全限制;请考虑恢复你的答案,因为它可能是更好的选择。我不知道!我也不确定它是否解决了这个问题——这两个线程仍然以某种方式相互碰撞。我尝试切换到StringBuilder,并从工作线程重复调用setText,但显示闪烁,JScrollPane从不滚动到底部。如果您愿意,我可以发布更新的代码,但我想我仍然需要一个从EDT更新的解决方案。@Etaoin:As@camickr notes,您看到的是缓冲。拿出它,更新就会顺利进行。对于简单的pIn码+1。读一下,我为什么没想到呢?。而且,使用JTextArea比使用JTextPane效率更高,因为您不必担心属性
这是一个很好的例子。太好了,谢谢你@jjnguy:我不知道为什么API链接是404Oracled,但是版本7已经放弃了线程安全的限制条件;请考虑恢复你的答案,因为它可能是更好的选择。我不知道!我也不确定它是否解决了这个问题——这两个线程仍然以某种方式相互碰撞。我尝试切换到StringBuilder,并从工作线程重复调用setText,但显示闪烁,JScrollPane从不滚动到底部。如果您愿意,我可以发布更新的代码,但我想我仍然需要一个从EDT更新的解决方案。@Etaoin:As@camickr notes,您看到的是缓冲。拿出它,更新就会顺利进行。对于简单的pIn码+1。读一下,我为什么没想到呢?。此外,使用JTextArea比使用JTextPane效率更高,因为您不必担心属性。太好了,谢谢你!JTextArea和append是另一种选择。谢谢,这看起来很有希望!奇怪的是,我试着将缓冲区大小调低到1,作为一个测试——更新的速度完全相同,只是现在每次更新一个字符。所以我不认为是缓冲区填满了。对不起,如果我暴露了我的无知;我以前从未尝试过使用PipedInputStream/PipedOutStream。@Etaoin,请参阅垃圾神的更新,以便在没有缓冲区的情况下读取数据。JTextArea和append是另一种选择。谢谢,这看起来很有希望!奇怪的是,我试着将缓冲区大小调低到1,作为一个测试——更新的速度完全相同,只是现在每次更新一个字符。所以我不认为是缓冲区填满了。对不起,如果我暴露了我的无知;我以前从未尝试过使用PipedInputStream/PipedOutStream。@Etaoin,请参阅垃圾神的更新,以在没有缓冲区的情况下读取数据。这实际上会创建一个新文档,因为您丢失了文档结构,即创建用于表示文档的元素。此外,可能与之关联的任何属性也将丢失。然后,您需要重新分析整个文本以重建文档结构。实际上,这会创建一个新文档,因为您会丢失文档结构,即创建用于表示文档的元素。此外,可能与之关联的任何属性也将丢失。然后需要重新分析整个文本以重建文档结构。