5

我正在用 JavaFX 2.2 编写一个多线程分形绘图程序,现在我需要一些指导。

我想要实现的是创建一个任务或服务(尚未决定),然后启动其他一些实际执行计算的任务,并在准备好时返回整个图像的部分。当所有的部分都返回到启动任务时,它会将这些部分放在一起并将其返回到主线程,以便可视化。

显然,所有这些都必须在不阻塞 UI 的情况下发生。

问题是我无法弄清楚这些任务如何相互通信。例如,我需要根据其中任务的平均进度(或类似的东西)更新启动任务的进度属性,因此它们的进度属性应该以某种方式绑定到启动任务的进度属性。图像片段应放在列表或某个容器中,并在所有图像都可用时在单独的图像上重绘。

我已经编写了这个程序的一个更简单(尽管仍然是实验性的)版本,它只创建一个计算整个分形的任务。进度绑定到 GUI 的 progressBar。任务成功后,返回值由 EventHandler 处理。

我不是在要求一个完整的解决方案,但是一些可能带有一些示例代码的想法确实会对我有所帮助。

这是应该修改的类:

package fractal;

import fractalUtil.DefaultPalette;
import fractalUtil.PaletteInterface;
import javafx.concurrent.Task;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import org.apache.commons.math3.complex.Complex;

/**
 *
 * @author athelionas
 */
public abstract class AbstractFractal extends Task implements FractalInterface {

    private PaletteInterface palette;
    protected final int width, height, order, iterations;
    protected final double scale, xReal, xIm, xCenter, yCenter, zoom;
    protected final boolean julia;

    protected AbstractFractal(final int width, final int height, final double xReal, final double xIm, final double xCenter, final double yCenter, final int order, final boolean julia, final int iterations, final double zoom) {
        this.width = width;
        this.height = height;
        this.xReal = xReal;
        this.xIm = xIm;
        this.xCenter = xCenter;
        this.yCenter = yCenter;
        this.order = order;
        this.julia = julia;
        this.iterations = iterations;
        this.zoom = zoom;
        this.scale = (double) width / (double) height;
        palette = new DefaultPalette();
    }

    @Override
    public final void setPalette(final PaletteInterface palette) {
        this.palette = palette;
    }

    @Override
    public abstract Complex formula(final Complex z, final Complex c, final int order, final Complex center);

    @Override
    public final Color calculatePoint(final Complex z, final Complex c, final int order, final Complex center, final int iterations) {
        Complex zTemp = z;
        int iter = iterations;
        while (zTemp.abs() <= 2.0 && iter > 0) {
            zTemp = formula(zTemp, c, order, center);
            iter--;
        }
        if (iter == 0) {
            return Color.rgb(0, 0, 0);
        } else {
            return palette.pickColor((double) (iterations - iter) / (double) iterations);
        }
    }

    @Override
    public final WritableImage call() {
        Complex z;
        Complex c;
        Complex center = new Complex(xCenter, yCenter);
        final WritableImage image = new WritableImage(width, height);

        if (julia) {
            c = new Complex(xReal, xIm);
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {

                    z = new Complex(((double) x) / (double) (width - 1) * 2.0 * scale * (1.0 / zoom) - scale * (1.0 / zoom), ((double) y) / (double) (height - 1) * 2.0 * (1.0 / zoom) - 1.0 * (1.0 / zoom));

                    image.getPixelWriter().setColor(x, y, calculatePoint(z, c, order, center, iterations));
                }

            }
        } else {
            z = new Complex(xReal, xIm);
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {

                    c = new Complex(((double) x) / (double) (width - 1) * 2.0 * scale * (1.0 / zoom) - scale * (1.0 / zoom), ((double) y) / (double) (height - 1) * 2.0 * (1.0 / zoom) - 1.0 * (1.0 / zoom));

                    image.getPixelWriter().setColor(x, y, calculatePoint(z, c, order, center, iterations));
                }
                updateProgress(y, height);
            }
        }
        return image;
    }
}
4

2 回答 2

6

使用绑定和Task. 这样,您根本不需要关心线程。您所需要的只是创建一个绑定,该绑定将根据线程数规范化每个进度并将它们总结起来。例如

progressBar.progressProperty().bind(
    task1.progressProperty().multiply(0.5).add(
         task2.progressProperty().multiply(0.5)));

对于未知数量的线程来说,这有点棘手。请参见下一个示例:

public class MultiProgressTask extends Application {
    private static final int THREADS_NUM = 10;

    // this is our Task which produces a Node and track progress
    private static class MyTask extends Task<Node> {

        private final int delay = new Random().nextInt(1000) + 100;
        { System.out.println("I update progress every " + delay); }

        @Override
        protected Node call() throws Exception {
            updateProgress(0, 5);
            for (int i = 0; i < 5; i++) {
                System.out.println(i);
                Thread.sleep(delay); // imitating activity
                updateProgress(i+1, 5);
            }
            System.out.println("done");
            return new Rectangle(20, 20, Color.RED);
        }
    };

    @Override
    public void start(Stage primaryStage) {
        ProgressBar pb = new ProgressBar(0);
        pb.setMinWidth(300);

        final VBox root = new VBox();
        root.getChildren().add(pb);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setScene(scene);
        primaryStage.show();

        DoubleBinding progress = null;

        for (int i = 0; i < THREADS_NUM; i++) {
            final MyTask mt = new MyTask();

            // here goes binding creation
            DoubleBinding scaledProgress = mt.progressProperty().divide(THREADS_NUM);
            if (progress == null) {
                progress = scaledProgress;
            } else {
                progress = progress.add(scaledProgress);
            }
            // here you process the result of MyTask
            mt.setOnSucceeded(new EventHandler<WorkerStateEvent>() {

                @Override
                public void handle(WorkerStateEvent t) {
                    root.getChildren().add((Node)t.getSource().getValue());
                }
            });
            new Thread(mt).start();
        }
        pb.progressProperty().bind(progress);
    }


    public static void main(String[] args) { launch(args); }
}
于 2012-10-21T09:12:28.107 回答
1

这是一个非常有趣的问题:)

如果我们暂时消除线程安全问题,您可以传入一个双属性(或任何进度属性绑定的)并使用进度更新它,然后更新进度指示器。有两个问题:

  1. 多个任务可以同时增加属性。
  2. 必须在 javafx 线程上触发更改。

我会用一个简单的 API 将属性包装在它自己的类中:

class ProgressModel {
    private final SimpleDoubleProperty progress;
    public void increment(finally double increment) {
        Platform.runLater(new Runnable() {
            progress.set(progress.doubleValue() + increment);
        }
    }

    public void bindPropertyToProgress(DoubleProperty property) {
        property.bind(progress);
    }
}

在上面的代码中,所有更新都将在 javafx 线程上按顺序运行,因此它是线程安全的并且没有锁。我已经完成了类似的后台任务,并且性能一直很好(在用户眼中是实时的),尽管如果您每秒更新数千次,情况可能并非如此!你只需要测量。我没有显示样板代码以使其更具可读性。

于 2012-10-20T09:59:56.013 回答