0

I am making a Java Swing application. Here is a simplified program logic:

public class Data
{
    //Data.
}

public class CustomPanel extends JPanel implements MouseListener
{
    private Data dataReference;
    @Override
    public void paintComponent(Graphics g)
    {
        //Represent data as images.
    }
    @Override
    public void mouseReleased(MouseEvent e)
    {
        new CustomSwingWorker().doInBackground();
    }

}

public class CustomSwingWorker extends SwingWorker<Void, Void>
{
    protected Void doInBackground() throws Exception
    {
        //Recalculate data. Very long process.
    }
    protected void done()
    {
        //Repaint GUI.
    }
}

When a user presses a mouse button, new SwingWorker background thread is created to recalculate the data in order not to block the GUI. Hovewer, now two threads have access to this data. Let's assume a user presses a mouse button and then resizes the window. SwingWorker thread recalculates data and at the same time EDT repaints CustomPanel using mutual data.

I understand that I can use syncronized methods. But if I synchronize data getters and setters, EDT can become blocked. This way I am back with the same problem that I tried to aviod in the first place - blocking the EDT.

Also keep in mind that this is not a producer-consumer problem. GUI can safely repaint itself using the old data. But it should not repaint itself with data that is in the middle of recalculation.

My question is this: what is the common solution to this kind of problem? What synchronization techniques should be used to guard mutual data when it is used in EDT?

  • 1) You should not invoke 8doInBackground()* directly. You should invoke the `execute()` method of the worker. 2) If you change the data in the worker then you should "publish" the results as the data is changed. The `process()` method of the worker is invoked when data is published and this method executes on the EDT. Read the section from the Swing tutorial on [Concurrency](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) for more information. 3) You can check out: https://stackoverflow.com/a/64196256/131872 for one example of this approach. There may be others. – camickr May 18 '23 at 14:23
  • You can also tell the users to wait while the data is recalculated. After all, they clicked the button. – Gilbert Le Blanc May 18 '23 at 14:32
  • @camickr thank you, but the problem is that EDT uses mutual data by itself when repainting CustomPanel with or without SwingWorker. EDT does not need to wait for SwingWorker to finish recalculating data. But when SwingWorker does recalculate data AND EDT repaints CustomPanel for any reason, EDT and SwingWorker share mutual data. – my_dear_doctor May 18 '23 at 15:59
  • You have asked a general question so we can only provide a general solution. Post your [mre] with a specific example of your problem. Then maybe someone can provide a specific solution. I don't see why the user resizing the frame is an issue since the data will not change, only the way the data is painted. – camickr May 18 '23 at 18:47

1 Answers1

0

The linked question in the first comment of the question already provides a solution to the mutual data sharing problem you describe, which is that you can just copy the intermediate data from the SwingWorker's thread to the EDT (via publish).

Copying data obviously allocates more memory and requires extra time, but no matter how big objects Data holds, the copy operation can be offloaded to the doInBackground method of the SwingWorker (when process is called on the EDT it will have its own exclusive copy of the data), thus not freezing the EDT (from the user's perspective). Remember that immutable data should not be copied (thus preserving memory and time). Only intermediate data needs to be copied, ie only data which changes as the background work goes on. Actually you may don't even have to copy the whole data that changes but instead of this you could copy the difference of the changed data (but that would depend on how efficiently the difference could be applied on the data that the EDT has access to).

As far as I understand from your post you have images in the Data class. In this case you could create the images inside the doInBackground and publish them for processing on the EDT. For an example (similar to the linked question), which is overly simplified (it doesn't even have getters and setters):

public class Data
{
    //Let's assume Data holds a single image (which is actually the only thing which differs
    //between publishes in the long background process for the sake of the example)
    Image img;
}

public class CustomPanel extends JPanel implements MouseListener
{
    private Data dataReference = new Data();
    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        // Get/access image from dataReference and draw it:
        g.drawImage(dataReference.img, 0, 0, this);
    }
    @Override
    public void mouseReleased(MouseEvent e)
    {
        new CustomSwingWorker(this).execute();
    }
    public void updateImage(Image img)
    {
        // Set/add/put the image into the dataReference (we are on the EDT):
        dataReference.img = img;
    }
}

public class CustomSwingWorker extends SwingWorker<Void, Image>
{
    private CustomPanel panel;
    public CustomSwingWorker(CustomPanel panel)
    {
        this.panel = panel;
    }
    protected Void doInBackground() throws Exception
    {
        /*
        For each step in the long process do:
           Recalculate data.
           Create a NEW image and draw on it.
           publish() the new image and ignore it aftewards.
        */
    }
    protected void process(List<Image> images)
    {
        //Here we are on the EDT...
        //Assume we are only interested in the last image each time (if multiple publishes occur before a single process method call):
        panel.updateImage(images.get(images.size() - 1));
    }
}

Update (addressing comments):

  1. But SwingWorker still need to get a copy of the original data. That's not always the case, it depends on what the SwingWorker needs for starting its background operation. An MRE in the question should help an/any answerer provide specific information oriented around your own specific problem/use-case, so that the solution is more obvious. In the sample code given in this answer there was no need to copy the panel's data to the SwingWorker upon its construction. There is no data copying in the code provided above, only references passed around plus the allocation of new data which is processed on the background thread (see the body of doInBackground method) and then published/passed to the EDT. If for any reason initialization data needs to be copied from the EDT to the SwingWorker then one possibility could be its constructor, but don't take my word for your scenario since I believe you haven't provided enough clarifications/details on it.
gthanop
  • 3,035
  • 2
  • 10
  • 27
  • I think I get it now. The key is that SwingWorker modifies a copy of the original data and then sets it with process() method on EDT. But SwingWorker still need to get a copy of the original data. And the only safe place to do it is in constructor, because it is invoked on EDT. Right? You say that data can be copied in doInBackground() method. But this method is invoked on a new thread. So what if I am in a process of getting a copy of the original data using doInBackground() on a new worker thread, but another SwingWorker at the same time is setting data using process() method on EDT? – my_dear_doctor May 19 '23 at 13:23
  • @my_dear_doctor *I think I get it now. The key is that SwingWorker modifies a copy of the original data and then sets it with process() method on EDT. But SwingWorker still need to get a copy of the original data. And the only safe place to do it is in constructor,* - that is exactly what the working example does in the link I provided with my original comment. Did you not follow the link and read the part where I state *the key is...*? – camickr May 19 '23 at 14:27
  • @camickr I read it hence the comment – my_dear_doctor May 19 '23 at 16:59