1

I'm in trouble in getting result from some threads. I explain the environment, I have a SWT shell with a button. The listener for this button calls a Runnable that inside its run() calls a method that instantiate N threads for performing some operations. The problem is: how can I display a message dialog or something on the screen, when all the computation is terminated? The code I have is something similar

public void widgetSelected(SelectionEvent event) {
    Runnable t = new MyThread(params);
    executor.execute(t);
  }

And inside MyThread class I have

public void run(){
   myMethod();
}

public void myMethod(){
   for(int i =0; i<queue.length; i++){
      Runnable thread = new AnotherThread();
      executor.execute(thread);
   }
   executor.shutdown();
   if(executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS){
     //here I know all thread of kind AnotherThread have finished
   }
}

So inside the widgetSelected method of the button listener, I want to put something that alerts the user that all threads called by the listener have successfully terminated. Is there a way to know that? If I put the awaitTermination statement inside the Listener, the shell becomes not responsive and freezes on the screen. Hope someone could help me. If something was not clear, please tell me. Thanks all.

Mirko
  • 137
  • 1
  • 2
  • 14
  • Have a look at this post: https://stackoverflow.com/questions/1250643/how-to-wait-for-all-threads-to-finish-using-executorservice/36718097#36718097 – Ravindra babu Jul 21 '17 at 11:17

2 Answers2

1

This is "straight forward" - but a bit of work: you "simply" have to enhance your Runnables to somehow signal their progress.

In other words: the ExecutorService interface doesn't offer any means to figure how many of scheduled "tasks" completed. So - if you need that information, you have to "bake" it into your Runnable.

One idea: create a Map<Runnable, Integer> upfront. The keys are your Runnable objects, and the value could represent some progress information. You start with all values at 0. And then you pass that map to each Runnable - and the Runnable simply updates its value at certain, defined points in time. Maybe ten steps, or maybe just 4. And as soon as all map values are at say 100, you know that you are done! That implies that your main thread simply loops and checks the map content every other second/minute/... and of course: this extra thread should not be the event dispatcher thread. You don't want to stall your UI while doing this.

( of course: you should use a ConcurrentHashMap to implement this ).

Long story short: this information is available already - the code in your Runnable knows what it is doing, right?! So you "only" have to make this information accessible to the outer world somehow. There are many options to do that; the above is just one way to get there.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • This seems like a bit of an overly-complicated approach, and forcing the main thread to constantly check for completion is not a good idea. This would cause the UI thread to be blocked during those checks, which causes the same unresponsiveness that OP is asking about. – avojak Jul 20 '17 at 22:30
  • A) Who says that *his* "main" thread is the event dispatcher thread? B) but point taken; I changed my answer accordingly C) but then: we are talking about iterating a map to check its values. If that gives enough churn to freeze the UI, then that system is already overloaded anyway! – GhostCat Jul 21 '17 at 06:00
  • In SWT, [events are dispatched from the main thread](http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2Fswt_threading.htm). I do agree that in practice your suggestion really shouldn't slow down the UI much, if at all, but the idea of doing that type of busy-waiting vs. using an event-based design gives me pause. – avojak Jul 21 '17 at 15:01
  • The fact that the great problems of our time are solved with PUSH approaches doesn't meant that small things that can easily be solved with PULL require the overhead of a PULL solution ;-) But I get your point, and of course, it is not by accident that things like CompleteableFutures are now part of Java9. – GhostCat Jul 21 '17 at 15:04
  • That is true - always a good reminder to consider the situation as a whole and not pick one approach for the sake of doing it that particular way! – avojak Jul 21 '17 at 15:17
1

I would recommend taking a look at using FutureCallbacks which are part of the Guava library.

What this will allow you to do is create a ListenableFuture for each task that you fire off. In your case, it sounds like this would be represented by a Runnable, but you can just as easily use a Callable. When these tasks are all fired off, you will end up with a list of these ListenableFuture objects, which can be "flattened" into a single ListenableFuture which represents the completion of ALL of the tasks. This is accomplished with the method Futures.allAsList(...):

final List<ListenableFuture<T>> futures = ...
final ListenableFuture<List<T>> combinedFuture = Future.allAsList(futures);

Now that you have a single ListenableFuture which represents the completion of all of your tasks, you can easily listen for its completion by adding a FutureCallback to be invoked upon completion:

Futures.addCallback(future, new FutureCallback<List<String>>() {
    @Override
    public void onFailure(final Throwable arg0) {
        // ...
    }

    @Override
    public void onSuccess(final List<String> arg0) {
        // ...
    }
}

Now, once these tasks are completed, we need to update the UI to notify users. To do so, we must be sure that the UI updates happen back on the SWT UI thread:

Display.getCurrent().asyncExec(new Runnable() {
    @Override
    public void run() {
        // Update the UI
    }
});

Note that this can easily be done within the onSuccess() method above so that the result of the tasks can be used.

Putting it all together, we can easily loop through a handful of ListeningExecutorService.submit(...) calls for background execution (so as not to block the UI thread - In my example below, you can freely type in the text box while the tasks are running in the background), grab all the ListenableFutures, and add a callback to be invoked upon completion, which will hop back to the UI thread to make the UI updates.


Full example:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

public class CallbackExample {

    private final Display display;
    private final Shell shell;
    private final Text output;
    private final ListeningExecutorService executor;

    public CallbackExample() {
        display = new Display();
        shell = new Shell(display);
        shell.setLayout(new FillLayout());

        executor = MoreExecutors.listeningDecorator(Executors
                .newFixedThreadPool(20));

        final Composite baseComposite = new Composite(shell, SWT.NONE);
        baseComposite
                .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        baseComposite.setLayout(new GridLayout());

        output = new Text(baseComposite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
        output.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        final Button button = new Button(baseComposite, SWT.PUSH);
        button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        button.setText("Start tasks");
        button.addSelectionListener(new SelectionAdapter() {

            @SuppressWarnings("synthetic-access")
            @Override
            public void widgetSelected(final SelectionEvent e) {
                // Start tasks when the button is clicked
                startTasks();
            }

        });

    }

    private void startTasks() {
        // Create a List to hold the ListenableFutures for the tasks
        final List<ListenableFuture<String>> futures = new ArrayList<ListenableFuture<String>>();
        // Submit all the tasks for execution (in this case 100)
        for (int i = 0; i < 100; i++) {
            final ListenableFuture<String> future = executor
                    .submit(new Callable<String>() {
                        @Override
                        public String call() throws Exception {
                            // Do the work! Here we sleep to simulate a long task
                            Thread.sleep(2000);
                            final long currentMillis = System
                                    .currentTimeMillis();
                            System.out.println("Task complete at "
                                    + currentMillis);
                            return "Task complete at " + currentMillis;
                        }
                    });
            // Add the future for this task to the list
            futures.add(future);
        }
        // Combine all of the futures into a single one that we can wait on
        final ListenableFuture<List<String>> future = Futures
                .allAsList(futures);
        // Add the callback for execution upon completion of ALL tasks
        Futures.addCallback(future, new FutureCallback<List<String>>() {

            @Override
            public void onFailure(final Throwable arg0) {
                System.out.println("> FAILURE");
            }

            @SuppressWarnings("synthetic-access")
            @Override
            public void onSuccess(final List<String> arg0) {
                System.out.println("> SUCCESS");
                // Update the UI on the SWT UI thread
                display.asyncExec(new Runnable() {

                    @Override
                    public void run() {
                        final StringBuilder sb = new StringBuilder();
                        for (final String s : arg0) {
                            sb.append(s + "\n");
                        }
                        final String resultString = sb.toString();
                        output.setText(resultString);
                    }

                });
            }

        });
    }

    public void run() {
        shell.setSize(200, 200);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        executor.shutdownNow();
        display.dispose();
    }

    public static void main(final String... args) {
        new CallbackExample().run();
    }

}
avojak
  • 2,342
  • 2
  • 26
  • 32
  • This solution has the drawback of introducing a dependency to a 3rd party library. Not everybody can easily do that ... – GhostCat Jul 21 '17 at 06:01