Swing JTextArea multithreading problem - InterruptedException
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
}
}
});
}
}