views:

449

answers:

3

I have made a swings application but there is one problem in that which is as follow:

I have initiated a SwingWorker thread named "Thread-Main" from the Event Dispatch Thread and have passed a JLabel reference of the GUI to the "Thread-Main".

Now I have started 10 threads from the "Thread-Main".

Now I want that all the 10 threads should update the JLabel.

How can I do that?

Someone told me that I can do this by first making all the 10 threads subclasses of SwingWorker and then calling the publish("") method and passing the string in that "publish" method and then collecting all the published strings by the following method in "Thread-Main"

@Override
protected void process(List<String> labelStrings) {
    String count = labelStrings.get(labelStrings.size() - 1);
    label.setText(count); // label is a reference to a label in the GUI 
}
  1. Is the above approach is correct to do that?
  2. Should the 10 threads be subclasses of SwingWorker?
  3. Is there any other way to do that?
A: 

why do you need 10 threads? why won't one extra thread do?

the real question: what is the problem you are trying to solve?

to answer your direct questions:

1) yes, that is the correct approach 2) yes, the threads should be SwingWorkers (if you are using netbeans, you can also use Tasks, which are subclasses of SwingWorker as well)

3) if you want to have a separate thread from the edt; then you need to use a swingworker; so that is the way to do it.

good luck!

Jill Renee
+3  A: 

Maybe an easier approach is to wrap the code that updates the GUI in a SwingUtilities.invokeLater(...) method.

Edit: In your individual threads whenever you want to update the label you do:

SwingUtilities.invokeLater(new Runnable()
{
    public void run()
    {
     label.setText(...);
    }
});
camickr
Can you please explain a bit which code to wrap and how?
Yatendra Goel
Yes this is good approach if you don't need to do any synchronisation between your GUI and your worker threads other than update the JLabel (It's far simpler than my solution!).
Adamski
If i follow this approach, then should the 10 threads started from the "Thread-Main" SwingWorker thread be subclasses of SwingWorker or Thread?
Yatendra Goel
You don't need to use a SwingWorker, you use normal Threads.
camickr
+1  A: 
  1. No - This is the incorrect approach if you want to control the number of threads yourself. Why? Because if you look at the SwingWorker code you'll see that it uses a ThreadPoolExecutor internally containing a maximum of 10 threads. If you kick off a number of SwingWorker's concurrently they will all run using this executor. However, you have no direct control over whether the background threads are executed in parallel.
  2. See point 1.
  3. My recommended solution would be:

    • Create a single SwingWorker.
    • Within the doInBackground() method kick off 10 threads either directly or by using an ExecutorService.
    • Use a CountDownLatch or CompletionService to synchronize between your master thread (i.e. SwingWorker background thread) and worker threads.

Example

Define the number of worker threads to invoke and declare the JLabel to be updated.

final int nThreads = 10;
JLabel myLbl = new JLabel();

Define the unit of work we wish to execute as a Callable<String>. The String result will be used to update the JLabel.

private static class MyCallable implements Callable<String> {
  public String call() { ... }
}

Now kick off a SwingWorker, which will in turn kick off multiple parallel workers to do any processing. The worker will not return a result via the call to done() (hence the Void type) but will marshall intermediate String results back onto the Swing thread by calling process(String... chunks).

new SwingWorker<Void, String>() {
  // See method definitions below.
}.execute();

Define doInBackground() to kick off the worker threads and block for each result using a CompletionService.

public Void doInBackground() throws Exception {
  // Define executor service containing exactly nThreads threads.
  ExecutorService execService = Executors.newFixedThreadPool(nThreads);


  // Define completion service that will contain the processing results.
  CompletionService compService = new ExecutorCompletionService(execService);    

  // Submit work to thread pool using the CompletionService.  Future<String>
  // instances will be added to the completion service's internal queue until complete.
  for (int i=0; i<nThreads; ++i) {
    compService.submit(new MyCallable());
  }

  // Take results from each worker as they appear and publish back to Swing thread.
  String result;
  while ((result = compService.take().get()) != null) {
    publish(result);
  }
}

Now we implement process(String... chunks) to simply update the JLabel when called.

public void process(String... chunks) {
  if (chunks.length > 0) {
    // Update label with last value in chunks in case multiple results arrive together.
    myLbl.setText(chunks[chunks.length - 1]);
  }
}

Finally we override done() to marshall any exceptions back onto the Swing thread.

public void done() {
  try {
    get(); // Will return null (as per Void type) but will also propagate exceptions.
  } catch(Exception ex) {
    JOptionPane.show ... // Show error in dialog.
  }
}
Adamski
Can you please explain how to use CountDownLatch. Which thread you means by master thread.
Yatendra Goel
I have given a worked example above. Rather that use CountDownLatch I've opted to use a CompletionService to capture the results. The code is untested!
Adamski
A very thanks for the answer.
Yatendra Goel