Как я могу позволить нескольким потокам рисовать на компоненте AWT?
РЕДАКТИРОВАТЬ: решено, посмотрите ниже для моего решения.
Прежде всего, это мой самый первый вопрос, поэтому, если я сделаю какие-либо ошибки, пожалуйста, скажите мне.
Я пытаюсь написать фрактальную программу Мандельброта на Java для учебных целей. Идеалом для всех функций, которые я хочу иметь, является Fractalizer ( http://www.fractalizer.de/en/), но сейчас я буду доволен программой, которая рисует набор Мандельброта на экране (вместо например, записав его в файл изображения). Конечно, я хочу, чтобы программа была быстрой, поэтому я подумал, что могу разделить вычисления на несколько потоков, чтобы использовать мой многоядерный процессор; например, в четырехъядерной системе изображение будет разделено на 2x2=4 изображения, каждое из которых будет рассчитываться отдельным потоком. Все эти потоки получают переданный объект Graphics, в котором они рисуют пиксели по мере их вычисления.
Моя первая попытка получить эту работу состояла в том, чтобы потоки рисовались на BufferedImage.getGraphics() и чтобы метод paint() постоянно вызывал repaint(), пока изображение не закончено:
g.drawImage(tempImg, 0, 0, null);
if (waiterThread.isAlive())
{
try
{
Thread.sleep(10);
} catch (InterruptedException e)
{
// do nothing
}
repaint(10);
}
(waiterThread объединяет все вычислительные потоки один за другим, поэтому, пока waiterThread жив, по крайней мере, один вычислительный поток еще не завершен.)
Это работает, но вызывает мерзкое мерцание на холсте из-за частой перекраски.
Затем с помощью небольшой тестовой программы я обнаружил, что Graphics.draw*any * рисует на экране мгновенно, до того, как метод рисования возвращается, поэтому мой текущий подход заключается в следующем:
- Одна панель с GridLayout, которая содержит 2x2 (в системе <4 ядра, 1x1) объекты MandelbrotCanvas
- Каждый объект MandelbrotCanvas при первом вызове paint() инициализирует вычислительный поток, передает ему собственный объект Graphics (на самом деле я использую собственный класс GroupGraphics, который передает один вызов Graphics нескольким графическим объектам, чтобы "сделать резервную копию" изображение в BufferedImage.getGraphics(), но это не важно), и запустите вычислительный поток.
- Панель в своем методе paint() извлекает вычислительные потоки из каждой из MandelbrotCanvases и присоединяет их.
К сожалению, это создает только черный экран. Только после завершения расчета изображение отображается.
Как правильно нарисовать несколько нитей на одном компоненте?
РЕДАКТИРОВАТЬ:
Чего я не знал: только нити диспетчеризации событий можно рисовать на компонентах AWT (грубо говоря), что означает, что последний подход, описанный выше, не может работать - очевидно, он должен выдавать исключение, но я не сделал получить один. Мое решение состоит в том, чтобы использовать первый подход - нарисовать изображение на BufferedImage и нарисовать его на Canvas - с единственной модификацией, которую я перегружаю методом update() для вызова метода paint() без очистки области рисования:
public void update(Graphics g)
{
paint(g);
}
Поэтому я думаю, что мой ответ на общий вопрос ("Как я могу позволить нескольким потокам рисовать на компоненте AWT?") Был бы: вы не можете, это не разрешено. Пусть потоки отрисовываются в BufferedImage.getGraphics() и многократно рисуют это изображение. Перегрузите метод update(), как описано выше, чтобы избежать мерцания. (Теперь это выглядит действительно великолепно.) Еще один совет, который я не могу использовать в моем случае, но все же хорош, заключается в том, что существует вариант repaint(), который принимает аргументы прямоугольника для указания области, которую необходимо перерисовать, и вариант, который принимает аргумент времени (в миллисекундах), поэтому перерисовка не должна происходить немедленно.
РЕДАКТИРОВАТЬ 2: Эта ссылка предоставляет очень полезную информацию: http://java.sun.com/products/jfc/tsc/articles/painting/index.html
2 ответа
Только GUI-поток может рисовать прямо на вашем компоненте.
Поэтому вы должны вызвать метод перекраски.
Если у вас есть фоновое вычисление, чтобы вызвать быстрое рисование, вы должны использовать версию с определенным временем.
Некоторые подробности здесь:
ПРИМЕЧАНИЕ. Если несколько вызовов метода repaint() происходят в компоненте до обработки начального запроса на перерисовку, несколько запросов могут быть объединены в один вызов update(). Алгоритм определения, когда несколько запросов должны быть свернуты, зависит от реализации. Если несколько запросов свернуты, результирующий прямоугольник обновления будет равен объединению прямоугольников, содержащихся в свернутых запросах.
Вы должны отправить запросы в EDT.
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
Rectangle r = myCurrentWorkingThread.getFinishedRectangle();
myPainter.repaint(r);
}
});
Идея состоит в том, что вы не будете перекрашивать пиксель за пикселем, а просто дадите большие куски рабочим потокам. Как только они заканчивают с единицей работы, они уведомляют главный объект (myPainter), который будет выполнять фактическую работу. Эта конструкция (EventQueue.invokeLater) гарантирует, что она будет в потоке диспетчера событий.