2

编辑:已解决,请在下面查找我的解决方案。

首先,这是我在这里的第一个问题,所以如果我犯了任何错误,请告诉我。

我正在尝试用 Java 编写一个 Mandelbrot 分形程序,用于培训目的。我想要的所有功能的理想选择是 Fractalizer ( http://www.fractalizer.de/en/ ),但现在,我会对在屏幕上绘制 Mandelbrot 集的程序感到满意(而不是,例如,将其写入图像文件)。当然,我希望程序快,所以我想我可以将计算分成多个线程来利用我的多核处理器;例如,在四核系统上,图像将被分成 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*anything* 在paint方法返回之前立即在屏幕上绘制,所以我目前的方法如下:

  • 一个具有 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() 的一个变体,它接受矩形参数来指定必须重绘的区域,并且一个需要时间参数(以毫秒为单位)的变体,因此不必立即进行重绘。

EDIT2:此链接提供了非常有用的信息:http: //java.sun.com/products/jfc/tsc/articles/painting/index.html

4

2 回答 2

2

只有 GUI 线程可以直接在您的组件上绘制。

所以你必须调用重绘方法。

当您有后台计算时,为了强制快速绘图,您应该使用以时间为参数的版本

来自这里的一些细节:

注意:如果在处理初始重绘请求之前在一个组件上多次调用 repaint(),则多个请求可能会合并为对 update() 的单个调用。确定何时应该折叠多个请求的算法取决于实现。如果折叠多个请求,则生成的更新矩形将等于折叠请求中包含的矩形的并集。

于 2012-06-27T11:16:35.913 回答
0

You have to send requests to the EDT.

        EventQueue.invokeLater(new Runnable() {

        @Override
        public void run() {
            Rectangle r = myCurrentWorkingThread.getFinishedRectangle();
            myPainter.repaint(r);
        }
    });

The idea is that you won't repaint pixel by pixel, rather giving larger chunks to the worker-threads. As soon as they're finished with a unit of work, they notify the main object (myPainter) that would do the actual work. This construct (EventQueue.invokeLater) will guarantee that it will be on the Event Dispatcher Thread.

于 2012-06-27T11:25:12.070 回答