Swing JTextArea multithreading problem - InterruptedException
I have a simple console application that runs calculations in several threads (10-20 of them). Now I'm trying to create a simple GUI that allows me to select the file to process and prints logs from all threads.
So, I created a swing GUI with JTextArea for my log and a method to log information to the log:
public synchronized void log(String text) {
logArea.append(text);
logArea.append(开发者_开发问答"\n");
if (logArea.getDocument().getLength() > 50000) {
try {
logArea.getDocument().remove(0,5000);
} catch (BadLocationException e) {
log.error("Can't clean log", e);
}
}
logArea.setCaretPosition(logArea.getDocument().getLength());
}
However, the setCaretPosition
method sometimes deadlocks on waiting some lock, and append
sometimes throws InterruptedException.
Exception in thread "Thread-401" java.lang.Error: Interrupted attempt to aquire write lock
at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1334)
at javax.swing.text.AbstractDocument.insertString(AbstractDocument.java:687)
at javax.swing.text.PlainDocument.insertString(PlainDocument.java:114)
at javax.swing.JTextArea.append(JTextArea.java:470)
at lt.quarko.aquila.scripts.ui.ScriptSessionFrame.log(ScriptSessionFrame.java:215)
I'm a total newbie in Swing, so I can not understand what am I doing wrong here?
Thanks in advance.
You don't want to manipulate Swing objects directly from another thread, you want to post manipulations to its event queue.
You should not update ui component from other threads, you should use EventDispatchThread. Here is the solution ;
public synchronized void log(String text) {
Runnable runnable = new Runnable() {
public void run(){
logArea.append(text);
logArea.append("\n");
if (logArea.getDocument().getLength() > 50000) {
try {
logArea.getDocument().remove(0, 5000);
} catch (BadLocationException e) {
log.error("Can't clean log", e);
}
}
logArea.setCaretPosition(logArea.getDocument().getLength());
}
}
SwingUtilities.invokeLater(runnable);
}
Max, you didn't mention, that you interrupt
the thread. But you surely did. So your question consists actually of 2 separate questions.
append
sometimes throws InterruptedException
I just fell into the same situation and don't know how to handle it. When I interrupt the thread, then Document.insertString
fails throwing this kind of error.
Others are not quite right about putting all in EDT thread. JTextArea.append
method is thread safe, so it needn't be wrapped. The only method you call that you should not (in work thread) is setCaretPosition
. So why you accept the invokeLater
answer? Probably because putting document access in one thread removed all locking problems. See AbstractDocument.writeLock
open jdk code, that explains a bit this Error
.
So it looks like putting Document
writes in EDT thread is really necessary, but only when one wants to interrupt the thread. And as a workaround for pretty unkind AbstractDocument
behaviour, that throws an Error
in this case.
I came up with the following workaround for Document
Error
. It's not quite clean, because the thread may be unfortunately interrupted right after setting bInterrupted
flag. But this may be avoided by performing Thread.interrupt()
in a controlled, synchronized way.
// test the flag and clear it (interrupted() method does clear it)
boolean bInterrupted = Thread.interrupted();
m_doc.insertString(m_doc.getLength(), s, null);
// restore the original interrupted state
if (bInterrupted)
Thread.currentThread().interrupt();
setCaretPosition
method sometimes deadlocks on waiting some lock
Here is my solution for caret update. I could simply go with invokeLater
, but I wanted to avoid superfluous calls, so I added an additional flag:
/** <code>true</code> when gui update scheduled. This flag is to avoid
* multiple overlapping updates, not to call
* <code>invokeLater</code> too frequently.
*/
private volatile boolean m_bUpdScheduled;
/** Updates output window so that the last line be visible */
protected void update()
{
if (!m_bUpdScheduled) {
m_bUpdScheduled = true;
EventQueue.invokeLater(new Runnable() {
public void run() {
m_bUpdScheduled = false;
try {
m_ebOut.setCaretPosition(m_doc.getLength());
}
catch (IllegalArgumentException iae) {
// doc not in sync with text field - too bad
}
}
});
}
}
I know this is an old post, but using the setCaretPosition() method for auto-scrolling the viewport to the bottom can lead to a deadlock problem if the frequency of updates to the JTextArea is very high. Having the updates only done on the EDT did not avoid the problem or provide any improvement.
Ways to avoid the problem:
- Do not attempt to auto-scroll, and set the caret update policy to NEVER_UPDATE. Likely not ideal, but in our main application, we have a "scroll lock" capability so output can be viewed while text is still being captured.
- Throttle the use of setCaretPosition(): If dealing with high volume of updates, set the caret after X number of updates. I have our application track diagnostics by lines, so I can defer the caret update each time until X number of lines are captured. Note, you still need to set caret update policy to NEVER_UPDATE if throttling.
- If your JTextArea is in a JScrollPane, then use the vertical scrollbar directly and reposition it to maximum position after each update to the text area. Make sure to use invokeLater() when repositioning the scrollbar so any size change that occurred due to text addition is taken into consideration.
The following is code fragment of a class I created for capturing text into a JTextComponent (or subclass thereof) that checks if scroll pane method is in effect:
if (autoScroll && (scrollPane != null)) {
final JScrollBar vertical = scrollPane.getVerticalScrollBar();
if (vertical != null) {
appendText(doc, txt);
java.awt.EventQueue.invokeLater(new Runnable() {
@Override public void run() {
vertical.setValue(vertical.getMaximum());
}
});
return;
}
}
If scroll pane is not available, then the caret-method is used, where throttling can be specified to minimize the risk of deadlock inside of swing code.
精彩评论