简体   繁体   中英

How to know if a Thread has finished its task in SWT environment?

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.

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.

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();
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM