2012-10-20 47 views
4

我正在用JavaFX 2.2编写一个多线程的分形绘图程序,现在我需要一些指导。JavaFX:更新多任务的进度

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

显然,所有这一切都必须发生,永远不会阻止用户界面。

问题是我找不出如何这些任务可以相互沟通。例如,我需要根据内部任务的平均进度(或类似的东西)更新启动任务的进度属性,因此他们的进度属性应该以某种方式绑定到启动任务的进度属性。图像块应放在一个列表或某个容器中,并在所有图像都可用时重新绘制在单独的图像上。

我已经写了一个简单的(但仍然是实验性的)这个程序的版本,只创建一个计算整个分形的任务。进度与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; 
    } 
} 

回答

5

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

+1我忘了任务已经建好了。 –

+0

非常感谢!这正是我几天来一直试图完成的事情。不幸的是这些文档相当稀少,所以再次感谢。现在我确信我可以自己找出其余的问题。 – Athelionas

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的线程按顺序运行,因此它是线程安全加上没有锁。我已经完成了类似的背景任务,并且性能一直很好(对用户来说是实时的),但如果您每秒更新数千次,这可能并非如此!你只需要测量。我没有显示锅炉板代码,使其更具可读性。