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
}
}
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With