Сделайте графический интерфейс более отзывчивым - PullRequest
4 голосов
/ 18 февраля 2010

Программа, которую я имею, визуализирует симуляцию физики (в основном). Прямо сейчас это работает, но может стать очень безразличным, и я думаю, я знаю почему - слишком много (читай: все) вычислений выполняется в потоке событий.

Когда нажата кнопка воспроизведения, создается Swing Timer, который периодически просыпается и вызывает updateTime() - пока все хорошо. Проблема заключается в том, что updateTime() выполняет итерацию через каждый зависящий от времени объект и указывает ему распространяться вперед во времени на соответствующую величину (либо прошедшее реальное время, либо произвольная единица времени на каждый тик). Все эти вычисления и последующие обновления графического интерфейса находятся в потоке диспетчеризации событий.

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

Моя лучшая идея на данный момент - создать интерфейс для каждого объекта, зависящего от времени, для реализации. Интерфейс будет указывать 2 метода, calcUpdateTime() и drawUpdateTime(). Я бы разделил каждый из их текущих updateTime() методов на физические вычисления (на calc_) и обновления GUI (на draw_). Тогда я бы создал только один класс SwingWorker, который принимает в конструктор объект TimeDependant, а его doInBackground будет вызывать calcUpdateTime, а done будет вызывать drawUpdateTime. Тогда я могу заменить все вхождения

myObj.updateTime(currentTime);

с

new MySwingWorker(myObj, currentTime).execute();

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

Спасибо, что прочитали это далеко.

Ответы [ 2 ]

2 голосов
/ 21 февраля 2010

Вы правы, нет необходимости вызывать SwingWorker.execute() для каждого работника для каждого тика, потому что вы будете создавать и уничтожать множество потоков, которые вам действительно не нужны.

Тем не менее, использование SwingWorker все еще является хорошей идеей, просто потому, что оно дает вам простой способ отделить код, который должен выполняться в фоновом режиме (ваша реализация SwingWorker.doInBackground()), от кода, который должен быть запущенным в Swing для последующего обновления GUI (ваша реализация SwingWorker.done()).

Вместо использования javax.swing.Timer или java.util.Timer, я бы порекомендовал использовать java.util.concurrent.ScheduledThreadPoolExecutor. По сути, он может делать все, что может java.util.Timer, за исключением того, что он также дает вам возможность контролировать, сколько потоков работает в фоновом режиме, как обрабатывать исключения, которые выбрасываются в фоновые потоки, и т. Д.

0 голосов
/ 18 февраля 2010

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

Я бы также посоветовал доверять вашим инстинктам о множественных рабочих экземплярах свинга каждый тик: мне это тоже не кажется правильным. (И согласно SwingWorker документам, он предназначен для запуска только один раз, поэтому вы не можете использовать его безопасно).

Таймер, который может делать то, что вам нужно, это java.util.Timer. У этого есть довольно много вариантов для указания таймаутов, хотя в зависимости от точности моделирования времени даже это может быть неуместно. (Например: если вы хотите, чтобы он выполнялся в режиме реального времени, но ваши вычисления на самом деле занимают больше времени, чем в реальном времени, что он должен делать? Запускать медленно или начать пропускать временные шаги?)

Итак, не зная точно, что включают в себя ваши процедуры расчета / отрисовки, я бы предположительно предложил попробовать java.util.Timer Когда он истекает (по истечении любого периода времени, который вы считаете подходящим на основе вашего «множителя реального времени»), пропустите все вызовы, а затем перебросьте результаты обратно в поток EDT для рисования (например, обернув их в SwingUtilities.invokeLater()).

Конечно, это может привести к проблемам с блокировкой, если EDT хочет ссылаться на те же объекты, что и поток вычислений. В идеале, если поток calc может передавать неизменяемые результаты в EDT, это избавит от необходимости вводить блокировки.

(Отказ от ответственности: ни одно из вышеперечисленного на самом деле не учитывает также несколько ядер / процессоров, кроме одного для GUI, одного для вычислений. Если вы хотите распараллелить приложение, это, вероятно, не подходящее решение).

...