views:

127

answers:

3

I am developing a Java Desktop Application. This app executes the same task public class MyTask implements Callable<MyObject> { in multiple thread simultaneously.

Now, when a user clicks on a "start" button, I have created a SwingWorker myWorker and have executed it.

Now, this myWorker creates multiple instances of MyTask and submits them to an ExecutorService.

Each MyTask instance has a loop and generates an intermediate result at every iteration. Now, I want to collect these intermediate results from each MyTask instances as soon as they are generated. Then after collecting these intermediate results from every MyTask instance, I want to publish it through SwingWorker.publish(MyObject) so that the progress is shown on the EDT.

Q1. How can I implement this? Should I make MyTask subclass of SwingWorker instead of Callable to get intermediate results also, because I think that Callable only returns final result.

Q2. If the answer of Q1. is yes, then can you give me a small example to show how can I get those intermediate results and aggregate them and then publish them from main SwingWorker?

Q3. If I can't use SwingWorker in this situation, then how can I implement this?

A: 

Take a look at ExecutorCompletionService<T>. It's an Executor that supplies a take method to retrieve the result of any completed task.

Update:

Extending SwingWorker will not do what you want as it is specifically intended for offloading work from the EDT to a background thread. You can't use it to offload work from a background thread to other background threads. Calls to SwingWorker.publish result in the equivalent of a SwingUtilities.invokeLater. There is no mechanism I am aware of for doing the same thing from a background thread to a background thread. Your best bet is to create your MyTask with a reference to a Queue and have your SwingWorker.doInBackground poll the queue for intermediate results.

Devon_C_Miller
@Devon I want to create several `SwingWorker` from one `SwingWorker mainWorker`. This `mainWorker` is initiated by EDT. The reason for creating several workers from the main worker is that I want to gather intermediate results of all those workers in the `mainWorker` and then publish those intermediate results via. `publish(...)` method of `mainWorker`.
Yatendra Goel
A: 

A SwingWorker is also a Future. As such it has the get() method which can be used inside the done() method to get the result of doInBackground() when that method finishes.

Thus the construct becomes somewhat like:

SwingWorker<T,P> sw=new SwingWorker<T,P>() {

  @Override
  public T doInBackground() throws Exception {
    T result;
    // do stuff here
    return result;
  }

  @Override
  public void done() {
    try {
      T result=get();
      // do stuff with result.
    }
    catch(ExecutionException e) {
      Exception fromDoInBackground= (Exception) e.getCause();
      // handle exception thrown from doInBackground()
    }
    catch(InterruptedException i) {
      // handle the case in which a SwingWorker was cancelled. typically: do nothing.
    }
  }
};
@user I request you to first read the question carefully before answering... I know how to use `SwingWorker` already. :)
Yatendra Goel
A: 

A1+A2. Yatendra, is it necessary that your Main SwingWorker must be the only one that passes interim results to the EDT? If your tasks were also SwingWorker instances, the Main Worker could delegate the responsability of sending interim results back to the EDT to them and just take care of the TaskWorkers life-cycle.

package threading;

import java.util.LinkedList;
import java.util.List;

import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

class MainSwingWorker extends SwingWorker<Void, Void> {
    private List<TaskWorker> tasks;

    public MainSwingWorker() {
        tasks = new LinkedList<TaskWorker>();
        for(int i=0; i<2; i++) 
            tasks.add(new TaskWorker(i));
    }

    @Override
    public Void doInBackground() throws Exception {
        Test.log("Building tasks.");                    
        for(TaskWorker task : tasks) 
            launch(task);
        Test.log("Waiting 5 secs.");
        Thread.sleep(5000);

        Test.log("Cancelling tasks");

        for(TaskWorker task : tasks ) 
            task.cancel(true);

        return null;
    }

    private void launch(final TaskWorker task) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Test.log("Launching task worker.");
                task.execute();
            }
        });     
    }
}

class TaskWorker extends SwingWorker<Void, String> {
    private int id;

    public TaskWorker(int wid) {
        id = wid;
    }

    @Override
    public Void doInBackground() throws Exception {     
        System.out.format("[%s] Starting worker %s\n", Thread.currentThread().getName(), id );
        while( !isCancelled() ) {
            // ***************************
            // your task process code here
            // ***************************
            publish(String.format("A dummy interim result #%s", id));
            Thread.sleep(1000);
        }       
        return null;
    }

    @Override
    public void process(List<String> results) {
        // it's pretty obvious, that once this method gets called you can safely 
        // call the Swing API from EDT among with the interim results
        for(String result : results )
            Test.log(result);
    }
}

public class Test {

    public static void log(String msg) {
        System.out.format("[%s] %s\n", Thread.currentThread().getName(), msg);
    }

    public static void main(String[] args) throws Exception {
        log("Init.");
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                log("Starting main worker.");
                MainSwingWorker worker = new MainSwingWorker();
                worker.execute();                           
            }
        });
        Thread.sleep(7000);
        log("Finished.");
    }
}

Keep mind that this is just a test, I know that there are a few ugly Thread.sleep(long) calls.

[main] Init.
[AWT-EventQueue-0] Starting main worker.
[SwingWorker-pool-1-thread-1] Building tasks.
[SwingWorker-pool-1-thread-1] Waiting 5 secs.
[AWT-EventQueue-0] Launching task worker.
[AWT-EventQueue-0] Launching task worker.
[SwingWorker-pool-1-thread-2] Starting worker 0
[SwingWorker-pool-1-thread-3] Starting worker 1
[AWT-EventQueue-0] A dummy interim result #1
[AWT-EventQueue-0] A dummy interim result #0
[AWT-EventQueue-0] A dummy interim result #0
[AWT-EventQueue-0] A dummy interim result #1
[AWT-EventQueue-0] A dummy interim result #1
[AWT-EventQueue-0] A dummy interim result #0
[AWT-EventQueue-0] A dummy interim result #0
[AWT-EventQueue-0] A dummy interim result #1
[AWT-EventQueue-0] A dummy interim result #0
[AWT-EventQueue-0] A dummy interim result #1
[SwingWorker-pool-1-thread-1] Cancelling tasks
[main] Finished.

A3 But if having another ExecutorService to schedule your task execution is a requirement in your project, I would implement a similar publish-process mechanism to perform communication between your Main Swing Worker Thread and that Task Thread. Although it seems to be repetitive, You may use a java.concurrent.ConcurrentQueue to store interim results as they become available?

PS: I just noticed a few days ago, but there is an annoying bug around SwingWorkers that prevents its ExecutorService from caching unused threads.

Vicente Reig