How to Make My Swingworker Example Work Properly

How do I make my SwingWorker example work properly?

Here an updated version of your code which works

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;

public class SwingTesting {

public static void main(String[] args) {
EventQueue.invokeLater( new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
JButton button = new JButton();
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new GuiWorker().execute();
}
});
button.setText("Test Me");
frame.getContentPane().add(button);
frame.pack();
frame.setVisible(true);
}
} );

}
}

class GuiWorker extends SwingWorker<Integer, Integer> {

/*
* This should just create a frame that will hold a progress bar until the
* work is done. Once done, it should remove the progress bar from the dialog
* and add a label saying the task complete.
*/

private JFrame frame = new JFrame();
private JDialog dialog = new JDialog(frame, "Swingworker test", true);
private JProgressBar progressBar = new JProgressBar();

public GuiWorker() {
progressBar.setString("Waiting on time");
progressBar.setStringPainted(true);
progressBar.setIndeterminate(true);
dialog.getContentPane().add(progressBar);
dialog.pack();
dialog.setModal( false );
dialog.setVisible(true);
}

@Override
protected Integer doInBackground() throws Exception {
System.out.println( "GuiWorker.doInBackground" );
Thread.sleep(1000);
return 0;
}

@Override
protected void done() {
System.out.println("done");
JLabel label = new JLabel("Task Complete");
dialog.getContentPane().remove(progressBar);
dialog.getContentPane().add(label);
dialog.getContentPane().validate();
}

}

Key point is that setting a model dialog visible blocks until the dialog is disposed. So making it non-modal fixed it + the validate call on the content pane when you switch components. I also adjusted your main method to run on the EDT, and added some System.out calls. If you remove the setModal( false ) call you will see those statements are not printed until you close the dialog

How to use SwingWorker in this example

Immediate fix

From documentation of SwingWorker

Type Parameters:

T - the result type returned by this
SwingWorker's doInBackground and get methods

V - the type used for
carrying out intermediate results by this SwingWorker's publish and
process methods

So replace

class Process extends SwingWorker<String, String>

with

class Process extends SwingWorker<DefaultTableModel, Void>

Note:

  • It's better if you rename your class from Process, there is already a class named [Process][2] in java.
  • You have to pass the String via SwingWorker's constructor. (In this case, have it as a member of Process class and pass it in constructor.

Calling the SwingWorker

In your actionPerformed method, replace

DefaultTableModel model = createModel(srtPath);

with

Process p = new Process(srtPath);
p.extecute();
DefaultTableModel model = p.get();
// Rest of actionPerformed content.

Doing it in background

Now everything is working, but you have not achieved the desired effect. The ui thread is still waiting for the output.

  • Make execute method last thing to do in actionPerformed method. (Ui thread will be released.)
  • Everything else after getting returned DefaultModel should be put in overridden done method of SwingWorker (Process). (You need to pass required objects from constructor).

    @Override
    protected void done() {
    table.setModel(model);
    table.setFillsViewportHeight(true);
    table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    TableColumn columnA = table.getColumn("#");
    columnA.setMinWidth(10);
    columnA.setMaxWidth(40);
    TableColumn columnB= table.getColumn("Start");
    columnB.setMinWidth(80);
    columnB.setMaxWidth(90);
    TableColumn columnC= table.getColumn("End");
    columnC.setMinWidth(80);
    columnC.setMaxWidth(90);
    }

    You have to pass table object from constrictor of Process.

Therefore in actionPerformed,

    Process p = new Process(srtPath, table);
p.execute();

Hope this helps.


Edit:
Constructor would be like following.

class Worker extends SwingWorker {

   private String srtPath;
private JTable table;

public Worker(String srtPath, JTable table) {
this.srtPath = srtPath;
this.table = table;
}

// Other methods.
}

How do I use SwingWorker in Java?

Generally, SwingWorker is used to perform long-running tasks in Swing.

Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up, so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive by which prioritizing the other AWT events before running the desired task (in the form of a Runnable).

However, the problem with SwingUtilities is that it didn't allow returning data from the the executed Runnable to the original method. This is what SwingWorker was designed to address.

The Java Tutorial has a section on SwingWorker.

Here's an example where a SwingWorker is used to execute a time-consuming task on a separate thread, and displays a message box a second later with the answer.

First off, a class extending SwingWorker will be made:

class AnswerWorker extends SwingWorker<Integer, Integer>
{
protected Integer doInBackground() throws Exception
{
// Do a time-consuming task.
Thread.sleep(1000);
return 42;
}

protected void done()
{
try
{
JOptionPane.showMessageDialog(f, get());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

The return type of the doInBackground and get methods are specified as the first type of the SwingWorker, and the second type is the type used to return for the publish and process methods, which are not used in this example.

Then, in order to invoke the SwingWorker, the execute method is called. In this example, we'll hook an ActionListener to a JButton to execute the AnswerWorker:

JButton b = new JButton("Answer!");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
new AnswerWorker().execute();
}
});

The above button can be added to a JFrame, and clicked on to get a message box a second later. The following can be used to initialize the GUI for a Swing application:

private void makeGUI()
{
final JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new FlowLayout());

// include: "class AnswerWorker" code here.
// include: "JButton" b code here.

f.getContentPane().add(b);
f.getContentPane().add(new JButton("Nothing"));
f.pack();
f.setVisible(true);
}

Once the application is run, there will be two buttons. One labeled "Answer!" and another "Nothing". When one clicks on the "Answer!" button, nothing will happen at first, but clicking on the "Nothing" button will work and demonstrate that the GUI is responsive.

And, one second later, the result of the AnswerWorker will appear in the message box.

java swingworker doesnt work properly it publish but wont process

Basically, from my observations, when this occurs, it's because what's going on in the doBackground method is preventing what ever is processing the "publish queue" from being able to run.

Try adding a call to Thread.yield or Thread.sleep somewhere in the processing loop to allow the "publishing" thread time to process the "publish queue"

import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class BackupBackgroundProcess extends SwingWorker<Object, String> {

public BackupBackgroundProcess() {
}

@Override
protected void process(List<String> list) {
System.out.println("PROCESSING " + list.size() + " files");
}

@Override
protected void done() {
}

@Override
protected Object doInBackground() throws Exception {
System.out.println("OK");
File[] root;
root = new File("/home/hilman/Pictures/err").listFiles();
for (File file : root) {
seekFiles(file, this);
}
return null;
}

private void seekFiles(File f, SwingWorker thread) {
if (f.isDirectory()) {
File[] listedFiles = f.listFiles();
for (int i = 0; i < listedFiles.length; i++) {
File file = listedFiles[i];
seekFiles(file, thread);
}
} else {
// System.out.println(" PUBLISHING FILE " + f.getAbsolutePath());
publish(f.getAbsolutePath());
Thread.yield();
}
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {

@Override
public void run() {
new BackupBackgroundProcess().execute();
}
});

}
}

Since you're doing a recursive call, I placed a call to Thread.yield after calling publish, but you could just as easily place it at the end of the seek method.

Remember, the process/publish process buffers results, so when publish is called, you might get a number of files, for example...

OK
PROCESSING 122 files
PROCESSING 127 files
PROCESSING 184 files
PROCESSING 144 files
PROCESSING 131 files
PROCESSING 147 files
PROCESSING 335 files
...

I tend to use Thread.sleep(1), but I'd recommending testing with both a see what results you get...

SwingWorker not responding

if I add Thread.sleep(...), it does work, though, it throws a
InterruptedException

The code that apparently produces the exception (copied from OP's edit):

while (!isCancelled()) {
counter %= arrNames.length;
// System.out.format("Counter : %d%n", counter);
publish(arrNames[counter]);
try {
Thread.sleep(30); // throws
} catch (InterruptedException ie) {
ie.printStackTrace();
}
counter++;
}

The reason, though, is the code that cancels the worker (in the actionListener):

backgroundTask.cancel(true);

which explicitly tells the worker to cancel by .. interrupting the thread. From its api doc:

mayInterruptIfRunning - true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete

As an aside: catching the exception and doing nothing (thereby effectively ignoring the interrupt) is not the best of ideas. Probably not too damaging in this case - due to checking the cancelled status. Typical worker implementations either catch and return, after doing some internal cleanup if needed, or don't handle it at all.

How to use SwingWorker?

At first, i just call the doInBackground() function from MySQL.execute(); using this. And then in doInBackground() function , i just collect those counter values and use publish(); function to passcertain value. here i just pass flag to denote data's were collected successfully.
publish(GraphLock);

After calling the Publish(); method, Process(List chunks) method get invoked. On that i just check the condition and call the Graph class to generate the graph.

    if(GraphLock==true)
SwingUtilities.invokeLater(new Graph());

It works properly...

WatchService and SwingWorker: how to do it correctly?

Actually, @Eels's comment didn't stop knocking in the back of my head - and finally registered: it's the way to go, but there is no need for any "artificial" struct, because we already have the perfect candidate - it's the PropertyChangeEvent itself :-)

Taking the overall process description from my question, the first three bullets remain the same

  • same: extend SwingWorker
  • same: do the registration stuff in the constructor
  • same: put the endless loop waiting for a key in doInBackground
  • changed: create the appropriate PropertyChangeEvent from each WatchEvent when retrieved via key.pollEvents and publish the PropertyChangeEvent
  • changed: fire the previously created event in process(chunks)

Revised FileWorker:

@SuppressWarnings("unchecked")
public class FileWorker extends SwingWorker<Void, PropertyChangeEvent> {

public static final String FILE_DELETED = StandardWatchEventKinds.ENTRY_DELETE.name();
public static final String FILE_CREATED = StandardWatchEventKinds.ENTRY_CREATE.name();
public static final String FILE_MODIFIED = StandardWatchEventKinds.ENTRY_MODIFY.name();

// final version will keep a map of keys/directories (just as in the tutorial example)
private Path directory;
private WatchService watcher;

public FileWorker(File file) throws IOException {
directory = file.toPath();
watcher = FileSystems.getDefault().newWatchService();
directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}

@Override
protected Void doInBackground() throws Exception {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return null;
}

for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
continue;
}
publish(createChangeEvent((WatchEvent<Path>) event, key));
}

// reset key return if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
break;
}
}
return null;
}

/**
* Creates and returns the change notification. This method is called from the
* worker thread while looping through the events as received from the Watchkey.
*
* @param event
* @param key
*/
protected PropertyChangeEvent createChangeEvent(WatchEvent<Path> event, WatchKey key) {
Path name = event.context();
// real world will lookup the directory from the key/directory map
Path child = directory.resolve(name);
PropertyChangeEvent e = new PropertyChangeEvent(this, event.kind().name(), null, child.toFile());
return e;
}

@Override
protected void process(List<PropertyChangeEvent> chunks) {
super.process(chunks);
for (PropertyChangeEvent event : chunks) {
getPropertyChangeSupport().firePropertyChange(event);
}
}
}

Do I use a SwingWorker?

It's best you split up your code into areas of responsibilities. Let's go with three: 1. the worker (ie the upload); 2. the display (ie the JLabel update); 3. integration of the two (the first two are independent of each other, so you'll need something to tie them together).

Abstracting from the actual work, you can use standard interfaces. The first one is just a Runnable, ie not taking any parameters and not returning anything. The second one is a Consumer<String> because it takes a String (to display) but doesn't return anything. The third will be your main control.

Let's start with the control because that's simple:

Consumer<String> display = createDisplay();
Runnable worker = createWorker();
CompletableFuture.runAsync(worker);

This will start the worker in a separate Thread which is what it sounds like you want.

So here's your uploader:

Consumer<String> display = // tbd, see below
Runnable worker = () -> {
String[] progress = {"start", "middle", "finish"};
for (String pr : progress) {
display.accept(pr);
Thread.sleep(1000); // insert your code here
}
}

Note that this worker actually does depend on the consumer; that is somewhat "unclean", but will do.

Now for the display. Having defined it as a Consumer<String>, it's abstract enough that we can just print the progress on the console.

Consumer<String> display = s -> System.out.printf("upload status: %s%n", s);

You however want to update a JLabel; so the consumer would look like

Consumer<String> display = s -> label.setText(s);
// for your code
s -> pleaseWaitWindow.getPleaseWaitLabel().setText(s);

Your actual question

So if you do that, you will notice that your label text doesn't get updated as you expect. That is because the label.setText(s) gets executed in the thread in which the worker is running; it needs to be inserted in the Swing thread. That's where the SwingWorker comes in.

The SwingWorker has a progress field which is what you can use for your labels; it also has a doInBackground() which is your actual upload worker thread. So you end up with

class UploadSwingWorker {
public void doInBackground() {
for(int i = 0; i < 3; i++) {
setProgress(i);
Thread.sleep(1000); // again, your upload code
}
}
}

So how does that update your label? The setProgress raises a PropertyChangeEvent you can intercept; this done using a PropertyChangeListener with the signature

void propertyChange(PropertyChangeEvent e)

This is a functional interface, so you can implement this with a lambda, in your case

String[] displays = {"start", "middle", "finish"};
updateLabelListener = e -> {
int index = ((Integer) e.getNewValue()).intValue(); // the progress you set
String value = displays[index];
label.setText(value);
}

and you can add it to the SwingWorker using

SwingWorker uploadWorker = new UploadSwingWorker();
uploadWorker.addPropertyChangeListener(updateLabelListener);
uploadWorker.execute(); // actually start the worker

Simpler

Note that I myself have never used a SwingWorker this way. The much simpler way to get around the problem that the GUI is not updated from within your worker thread is to call the GUI update using SwingUtilities.invokeLater().

Coming back to the initial Consumer<String> I brought up, you can do

    Consumer<String> display = s -> SwingUtilities.invokeLater(
() -> pleaseWaitWindow.getPleaseWaitLabel().setText(s)
);

and that should do. This allows you to keep your worker in the more abstract Runnable and use the usual scheduling mechanisms to run it (ExecutorService.submit() or CompletableFuture.runAsync() for example), while still allowing to update the GUI on a similarly simple level.

Background SwingWorker thread not executing a particular piece of code

Immediately off the bat I see that you're making Swing calls from within the SwingWorker's doInBackground, something which completely defeats the purpose of using a SwingWorker or background thread. Also, the call you're making, one that displays a modal dialog:

waitingDialog.setVisible(true); // **** here ****
MyCustomClass.run(); // **** not sure what this does or if this makes Swing calls or not.

will pause all code flow below it until the dialog is no longer visible, as that is how modal dialogs function. This is what is blocking your SwingWorker. You must try to separate your code so that Swing calls are only made on the Swing event thread, and use the worker for only the background work.

Instead

  • Create your SwingWorker
  • Execute your SwingWorker
  • Then call setVisible(true) on your modal dialog

In pseudocode:

create modal dialog
create SwingWorker
Attach any PropertyChangeListeners to the SwingWorker (if needed)
Execute the SwingWorker
Display the modal dialog

So, the first quick change I would make in your code above would be to remove waitingDialog.setVisible(true); from your SwingWorker doInBackground() method, and then call the waitingDialog.setVisible(true); method AFTER executing the SwingWorker, i.e., after backgroundThread.execute(); in particular. I know it seems counter-intuitive to call dispose on it seemingly before making it visible, but trust me, it will work better this way, since the dispose call will be blocked by the time it takes to run the background thread.



Related Topics



Leave a reply



Submit