JAVAFX:更新多个任务的进度



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

我要实现的是创建一个任务或服务(尚未决定),然后启动其他一些任务,这些任务实际上可以完成整个图像的计算和返回部分。当将所有部件返回到启动任务时,它将零件放在一起并将其返回到主线程中,以便可以看到。

显然,所有这些都必须发生,而不会阻止UI。

问题是我无法弄清楚这些任务如何相互通信。例如,我需要根据内部任务的平均进度(或类似的内容)来更新启动任务的进度属性,因此他们的进度属性应以某种方式绑定到启动任务的进度属性。图像件应放在列表或某些容器中,并在所有图像中重新绘制。

我已经编写了该程序的简单(尽管仍然是实验性的)版本,该版本仅创建一个计算整个分形的任务。进度与GUI的进度栏绑定在一起。返回值由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;
    }
}

使用绑定和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); }
}

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

如果我们暂时删除线程安全性问题,则可以通过双重属性(或任何进度属性所限制)并随后更新进度指标的进度进行更新。两个问题:

  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线程上依次运行,因此它是线程安全加上锁定的。我完成了类似的背景任务,并且性能很好(对用户的眼睛实时),尽管如果您每秒更新数千次,情况可能并非如此!您只需要测量即可。我没有显示锅炉板代码,以使其更具可读性。

最新更新