我正在用JavaFX 2.2编写一个多线程分形绘图程序,现在我需要一些指导。
我想要实现的是创建一个任务或服务(还没有决定),然后启动一些实际进行计算的其他任务,并在准备好时返回整个图像的部分。当所有部分都返回到启动任务时,它将这些部分放在一起并将其返回到主线程,以便可以看到它。
显然,所有这一切都必须在没有阻止UI的情况下发生。
问题是我无法弄清楚这些任务如何相互通信。例如,我需要根据其中任务的平均进度(或类似的东西)更新启动任务的progress属性,因此它们的进度属性应该以某种方式绑定到启动任务的progress属性。当所有图像片段都可用时,应将图像片放入列表或某个容器中并重新绘制在单独的图像上。
我已经编写了一个更简单(虽然仍然是实验性的)该程序版本,它只创建了一个计算整个分形的任务。进度绑定到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;
}
}
答案 0 :(得分:6)
使用绑定和Task
。这样您根本不需要关心线程。您所需要的只是创建一个绑定,它将根据线程数对每个进度进行规范化并汇总它们。 E.g。
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 :(得分:1)
这是一个非常有趣的问题:)
如果我们暂时删除线程安全问题,您可以传入double属性(或者任何progress属性绑定的内容)并使用进度更新该属性,然后更新进度指示器。两个问题:
我会使用一个简单的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线程上运行,因此它是线程安全加上没有锁。我已经完成了类似的后台任务,性能一直很好(实时到用户的眼睛)虽然如果你每秒更新数千次,可能不是这样!你只需要衡量。我没有展示锅炉板代码,使其更具可读性。