SecondaryLoop.enter () не блокируется до вызова exit () в EDT - PullRequest
4 голосов
/ 01 октября 2019

Сводка

По какой-то причине, когда я вызываю SecondaryLoop.enter() в потоке диспетчеризации событий AWT (EDT), он не ожидает SecondaryLoop.exit() длябыть вызванным перед разблокировкой.

Справочная информация

Поскольку я думаю, что SecondaryLoop не очень известный класс, я дам краткий обзор:

В общем,плохая идея иметь какой-либо долго выполняющийся или блокирующий код, работающий на EDT, потому что тогда ваше приложение не будет реагировать на какие-либо события, пока этот код не завершится. EventQueue.createSecondaryLoop() позволяет вам создать новый цикл обработки событий, который будет обрабатывать события, позволяя блокировать EDT без потери скорости отклика. Это то, что используются в модальных диалоговых окнах свинга, чтобы позволить вам блокировать EDT, пока вы ждете закрытия диалогового окна, но при этом все элементы управления в самом диалоговом окне могут работать.

После создания SecondaryLoopНапример, вы должны иметь возможность вызывать enter(), и он должен блокироваться до тех пор, пока не будет вызван exit().

Из документов

Этот метод может вызываться любым потоком, включаяпоток рассылки событий. Этот поток будет заблокирован до тех пор, пока не будет вызван метод exit () или цикл не будет завершен. Новый поток будет создан в потоке диспетчеризации событий для диспетчеризации событий в любом случае.

Я не совсем уверен, что это значит, когда он говорит "или цикл завершается", хотя. Это может быть моей проблемой.

Тестовый код

Вызов метода enter() в потоке, отличном от EDT, блокируется, как и следовало ожидать:

System.out.println("Enter Loop");
Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop().enter();
System.out.println("Done (we should never get here)");

Вывод:

Enter Loop

Однако, если мы вызываем его на EDT, он блокируется примерно на секунду, но затем продолжает:

System.out.println("Enter Loop");
try {
    SwingUtilities.invokeAndWait(() -> Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop().enter());
} catch (InvocationTargetException | InterruptedException e) {
    e.printStackTrace();
}
System.out.println("Done (we should never get here)");

Вывод:

Enter Loop
Done (we should never get here)

Согласно комментарию tevemadar (спасибо BTW), я обновил код, чтобы предотвратить возможные проблемы со сборкой мусора:

//Storing loop in array as a quick hack to get past the
// "final or effectively final" issue when using this in the invokeAndWait
SecondaryLoop loop[] = new SecondaryLoop[1];

System.out.println("Enter Loop");
try {
    SwingUtilities.invokeAndWait(() -> {
        loop[0] = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
        loop[0].enter();
    });
} catch (InvocationTargetException | InterruptedException e) {
    e.printStackTrace();
}
System.out.println("Done (we should never get here)");
//Just printing this to make sure that it is used after the invokeAndWait is done. This is just
//to make sure there isn't some sort of optimization thing that is deciding that we don't
//need this anymore and allowing the loop to be garbage collected
System.out.println(loop[0]);

Вывод:

Enter Loop
Done (we should never get here)
java.awt.WaitDispatchSupport@2401f4c3

Итак, покаэто было хорошее предложение, которое, похоже, не является моей проблемой.

Это кажется довольно противоречивым для документации (и для меня цели всего SecondaryLoop. Я что-то упустил?

Среда

ОС: Windows 10

Java:

C:\Program Files\Java\jre8\bin>java.exe -version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

Обновление

На догадку я попытался добавить таймер, который постоянно добавляет больше событийв цикл EDT. Похоже, что добавление таймера поддерживает цикл и блокирует его:

//Add a keep alive timer which adds an event to the EDT for every 0.5 sec
new Timer(500, null).start();

System.out.println("Enter Loop");
try {
    SwingUtilities.invokeAndWait(() -> Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop().enter());
} catch (InvocationTargetException | InterruptedException e) {
    e.printStackTrace();
}
System.out.println("Done (we should never get here)");

С этим кодом онзависает, как я ожидал, и если я через некоторое время вставлю в цикл некоторый код, вызывающий метод exit(), он завершится, как я и ожидал. Таким образом, кажется, что цикл должен завершиться сам, как только он прошел определенное количество времени без события (но только если он был первоначально вызван из EDT по какой-то причине ...).

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

1 Ответ

1 голос
/ 03 октября 2019

Разобрался (по крайней мере, с этой конкретной проблемой, у меня все еще есть некоторые связанные проблемы, но я надеюсь, что смогу решить их самостоятельно).

Я решил начать отладку вИсходный код Java, и я понял, что мой поток был разблокирован из-за этого сегмента в java.awt.EventQueue:

    /**
     * Called from dispatchEvent() under a correct AccessControlContext
     */
    private void dispatchEventImpl(final AWTEvent event, final Object src) {
        event.isPosted = true;
        if (event instanceof ActiveEvent) {
            // This could become the sole method of dispatching in time.
            setCurrentEventAndMostRecentTimeImpl(event);
            ((ActiveEvent)event).dispatch();
        } else if (src instanceof Component) {
            ((Component)src).dispatchEvent(event);
            event.dispatched();
        } else if (src instanceof MenuComponent) {
            ((MenuComponent)src).dispatchEvent(event);
        } else if (src instanceof TrayIcon) {
            ((TrayIcon)src).dispatchEvent(event);
        } else if (src instanceof AWTAutoShutdown) {
            if (noEvents()) {
                dispatchThread.stopDispatching();
            }
        } else {
            if (getEventLog().isLoggable(PlatformLogger.Level.FINE)) {
                getEventLog().fine("Unable to dispatch event: " + event);
            }
        }
    }

В моем случае src было AWTAutoShutdown, что привело к тому, что мой вторичный цикл завершился доЯ позвонил exit().

Я нашел эту статью , которая объясняет, что для того, чтобы убедиться, что очередь событий в конечном итоге завершается, когда все окна расположены, awt определяет, когда все компоненты больше не отображаютсяи очередь событий пуста, затем ждет в течение 1 секунды, затем запускает событие с классом AWTAutoShutdown в качестве источника, который завершает очередь событий и позволяет JVM завершаться. Этот 1-секундный тайм-аут, поэтому я заметил, что он немного зависает.

Это объясняет, почему добавление таймера заставило мой код работать (так как я добавлял событие каждые полсекунды и время ожидания для AWTAutoShutdownравен 1 секунде, очередь событий будет поддерживаться в рабочем состоянии).

Вариант использования для всего этого заключается в создании в основном EDT-безопасного семафора, который позволит выполнять события, даже если они ожидаются вEDT (который я использую для отображения диалогов JavaFX из приложения Swing и для того, чтобы он вел себя как собственный модальный диалог Swing). Так что в моем реальном случае использования это должно работать просто отлично (потому что в моих реальных приложениях всегда должен быть какой-то компонент свинга). Тем не менее, я даже не опробовал свой фактический вариант использования. Будучи большим сторонником TDD, я сначала сосредоточился на своих тестах JUnit, которые фактически не создавали никаких компонентов пользовательского интерфейса.

Итак, я провел быстрый тест с небольшим фиктивным приложением с графическим интерфейсом,работает просто отлично. Я просто собираюсь добавить свой таймер на 500 мс в мои модульные тесты и запускать его и останавливать перед каждым тестом.

После этого я все еще сталкиваюсь с некоторыми проблемами с некоторыми из моих тестов, номой минимальный проверяемый пример из моего исходного вопроса работает просто отлично. Я покопаюсь в оставшихся неудачах теста и, надеюсь, выясню их самостоятельно. Если это кажется связанной проблемой, тогда я просто добавлю новый вопрос SO и поставлю здесь ссылку.

...