简体   繁体   中英

Java Executor for “main” thread

I'd like to create an Executor from the "main" thread of a program (similar to the main looper in Android), and then just run until it has processed everything submitted to it:

public class MyApp {
  private static Callable<Integer> task = () -> {
    // ... return an int somehow ...
  };

  public static void main(String[] args) throws ExecutionException, InterruptedException {
    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
    Thread main = Thread.currentThread();
    ExecutorService executorService = Executors.newSingleThreadExecutor(r -> main);

    service.submit(task).addListener(() -> {
      /// ... do something with the result ...
    }, executorService);

    executorService.awaitTermination(100, TimeUnit.SECONDS);
  }
}

But I get an IllegalThreadState exception:

SEVERE: RuntimeException while executing runnable MyApp$$Lambda$20/0x00000008000a6440@71f06a3c with executor java.util.concurrent.Executors$FinalizableDelegatedExecutorService@47add263
java.lang.IllegalThreadStateException
    at java.base/java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:926)
    at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1343)
    at java.base/java.util.concurrent.Executors$DelegatedExecutorService.execute(Executors.java:687)
    at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1137)
    at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:957)
    at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:726)
    at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.afterRanInterruptibly(TrustedListenableFutureTask.java:131)
    at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:133)
    at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:78)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

I could just start an ExecutorService on a new thread, and then await that, but that seems wasteful.

Is there a good way to create an Executor from the current thread, and wait for it to process everything that has been submitted to it?

You are perhaps looking for something like a CompletionService : you submit tasks to this, and it gives you them back in order of completion.

ExecutorService service = Executors.newFixedThreadPool(1);
ExecutorCompletionService<Integer> comp = new ExecutorCompletionService<>(service);

comp.submit(task);
// ... I assume you want to submit N tasks.

for (int i = 0; i < N; ++i) {
  Future<Integer> future = comp.take();
  Integer result = future.get();

  // ... do something with the result ...
}

This will process the items as soon as they are complete. If you are able to wait until you have everything, and then process it in one go, you can use Guava's Futures.allAsList :

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
List<Callable<Integer>> tasks = ...;

List<ListenableFuture<Integer>> futures = new ArrayList<>();
for (Callable<Integer> task : tasks) {
  futures.add(service.submit(task));
}

ListenableFuture<List<Integer>> resultsFuture = Futures.allAsList(futures);
List<Integer> results = resultsFuture.get();

// Process the results.

Use Guava's MoreExecutors.newDirectExecutorService()

This will make sure that the code submitted will be executed in the same thread of the ThreadPool. I know, it's not the main thread, but at least you don't create other new threads just for the listener, just like you wanted.

import com.google.common.util.concurrent.*;
import org.junit.jupiter.api.Test;
import java.util.concurrent.*;

class ExecutorTest {
  private static Callable<Integer> task = () -> {
    System.out.println("in call: " + Thread.currentThread().getName());
    TimeUnit.SECONDS.sleep(1);
    return 0;
  };
  @Test
  void test() throws InterruptedException {
    ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
    ExecutorService executor = MoreExecutors.newDirectExecutorService();
    service.submit(task).addListener(() -> {
      System.out.println("in listener: " + Thread.currentThread().getName());
    }, executor);
    executor.awaitTermination(2, TimeUnit.SECONDS);
  }
}

The method invokeAll() documented here waits for all tasks to complete
You can do this

executorService.invokeAll();

Follow this post for more explanations

You are trying to execute the callback in the main thread. The straightforward approach is to call get() :

YourResult result = service.submit(task).get();
/* Do something with your result... */

If you want to execute a callback asynchronously, you might be interested in a CompletionStage

CompletableFuture.runAsync(task).thenAccept(r -> /* do something with result */);

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