Как я могу позволить нескольким потокам рисовать на компоненте 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) гарантирует, что она будет в потоке диспетчера событий.

Другие вопросы по тегам