aaronstacy
aaronstacy

Reputation: 6428

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?

Upvotes: 1

Views: 5226

Answers (4)

Olivier Gr&#233;goire
Olivier Gr&#233;goire

Reputation: 35437

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

Upvotes: 2

Andy Turner
Andy Turner

Reputation: 140318

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.

Upvotes: 0

erickson
erickson

Reputation: 269697

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 */);

Upvotes: 0

IQbrod
IQbrod

Reputation: 2265

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

executorService.invokeAll();

Follow this post for more explanations

Upvotes: 0

Related Questions