JavaFX: updating progress for multiple tasks

I am writing a multi-threaded fractal drawing program using JavaFX 2.2, and now I need to be guided.

What I'm trying to achieve is to create a task or service (not yet decided) that then launches some other tasks that actually do the calculations and return sections of the whole image when they are ready. When all the parts return to the initiating task, they stack the pieces and return them to the main stream so that they can be visualized.

Obviously, all this should happen without blocking the user interface.

The problem is that I cannot understand how these tasks can interact with each other. For example, I need to update the progress property of the initiating task based on the average progress of the tasks inside it (or something like this), so their progress properties should be tied to the progress property of the initiating task. Parts of the image should be placed in a list or some container and redraw on a separate image when they are all available.

I have already written a simpler (albeit still experimental) version of this program that creates only one task, which calculates the entire fractal. Progress is associated with the progressBar GUI. The return value is processed by the EventHandler when the task succeeds.

I am not asking for a complete solution, but some ideas, perhaps a little example code, will really help me.

This is the class that should be changed:

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
source share
2 answers

Use binding and Task . This way you don’t have to worry about streaming at all. All you need to do is create a binding that will normalize each progress according to the number of threads and summarize them. For instance.

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

This is a bit trickier for an unknown number of threads. See the following example:

 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); } } 
+5
source

This is a pretty interesting problem :)

If we fix the thread safety issue for a moment, you can pass the double property (or whatever the progress property) and update it with progress, which then updates the progress indicator. Two problems with this:

  • Several tasks can simultaneously increase a property.
  • Changes must be activated in javafx thread.

I would wrap the property in my class with a simple 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); } } 

In the above code, all updates will be performed in the javafx thread sequentially so that it is thread safe and also without blocking. I did similar background tasks like this, and the performance was good (in real time for users), although if you update thousands of times per second, it might not be so! You just need to measure. I did not show the boiler plate code to make it more readable.

+1
source

Source: https://habr.com/ru/post/1440939/


All Articles