Swing JTextArea - проблема многопоточности - InterruptedException - PullRequest
6 голосов
/ 22 декабря 2010

У меня есть простое консольное приложение, которое выполняет вычисления в нескольких потоках (10-20 из них).Сейчас я пытаюсь создать простой графический интерфейс, который позволяет мне выбирать файл для обработки и печатать журналы из всех потоков.

Итак, я создал графический интерфейс Swing с JTextArea для моего журнала и метод для регистрации информациив журнал:

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());
}

Однако метод setCaretPosition иногда блокируется при ожидании какой-либо блокировки, а append иногда вызывает 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)

Я новичокв свинге, так что не могу понять, что я тут не так делаю?

Заранее спасибо.

Ответы [ 4 ]

5 голосов
/ 22 декабря 2010

Вы не хотите манипулировать объектами Swing напрямую из другого потока, вы хотите опубликовать манипуляции в его очереди событий .

3 голосов
/ 22 декабря 2010

Вы не должны обновлять пользовательский интерфейс из других потоков, вы должны использовать EventDispatchThread.Вот решение;

 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);

    }
2 голосов
/ 07 октября 2012

Макс, ты не упомянул, что ты interrupt нить.Но ты, конечно, сделал.Таким образом, ваш вопрос состоит из двух отдельных вопросов.

append иногда выдает InterruptedException

Я просто попал в ту же ситуацию и не знаю, как с ней справиться,Когда я прерываю поток, то Document.insertString перестает выдавать ошибку такого рода.

Другие не совсем правы относительно помещения всех в поток EDT.JTextArea.append метод является потокобезопасным, поэтому его не нужно переносить.Единственный метод, который вы не должны вызывать (в рабочем потоке) - это setCaretPosition.Так почему вы принимаете ответ invokeLater?Вероятно, потому что помещение доступа к документу в один поток устранило все проблемы с блокировкой.См. AbstractDocument.writeLock open jdk code , который немного объясняет это Error.

Так что похоже, что запись Document в поток EDT действительно необходима, но только тогда, когда кто-то хочетпрервать поток.И как обходной путь для довольно недоброго поведения AbstractDocument, который в этом случае выдает Error.

Я предложил следующий обходной путь для Document Error.Это не совсем чисто, потому что поток, к сожалению, может быть прерван сразу после установки флага bInterrupted.Но этого можно избежать, выполнив Thread.interrupt() контролируемым, синхронизированным способом.

// 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 метод иногда блокируется при ожидании некоторой блокировки

Вотмое решение для обновления каретки.Я мог просто пойти с invokeLater, но я хотел избежать лишних звонков, поэтому я добавил дополнительный флаг:

/** <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
          }
        }
    });
  }
}
0 голосов
/ 05 сентября 2018

Я знаю, что это старый пост, но использование метода setCaretPosition () для автоматической прокрутки области просмотра вниз может привести к проблеме взаимоблокировки, если частота обновлений JTextArea очень высока.Обновления, сделанные только на EDT, не помогли избежать проблемы или обеспечить ее улучшение.

Способы избежать этой проблемы:

  • Не пытайтесь выполнять автоматическую прокрутку и установитеполитика обновления каретки до NEVER_UPDATE.Скорее всего, не идеально, но в нашем основном приложении у нас есть возможность «блокировки прокрутки», поэтому вывод можно просматривать, пока текст еще захватывается.
  • Отрегулируйте использование setCaretPosition (): если вы работаете с большим объемомобновления, установите каретку после X числа обновлений.У меня есть приложение, которое отслеживает диагностику по линиям, поэтому я могу откладывать обновление каретки каждый раз до тех пор, пока не будет зафиксировано количество X строк.Обратите внимание, что вам все равно нужно установить политику обновления каретки на NEVER_UPDATE, если регулирование.
  • Если ваша JTextArea находится в JScrollPane, тогда используйте вертикальную полосу прокрутки напрямую и устанавливайте ее в максимальное положение после каждого обновления текстовой области.Обязательно используйте invokeLater () при изменении положения полосы прокрутки, чтобы учитывалось любое изменение размера, произошедшее из-за добавления текста.

Ниже приведен фрагмент кода класса, созданного мной для захвата текста вJTextComponent (или его подкласс), который проверяет, действует ли метод области прокрутки:

  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;
    }
  }

Если панель прокрутки недоступна, то используется метод вставки, где можно задать регулирование, чтобы минимизировать рисктупик внутри свинг-кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...