简体   繁体   中英

Timeout for ExecutorService without blocking the main thread

I would like to execute some work in a background with a time limit. The thing is, I don't want to block the main thread.

Naive implementation is to have two executor services. One for scheduling/timeout and the second one will be responsible for getting work done.

final ExecutorService backgroundExecutor = Executors.newSingleThreadExecutor();
final ExecutorService workerExecutor = Executors.newCachedThreadExecutor();


backgroundExecutor.execute(new Runnable() {
    public void run() {
        Future future = workerExecutor.submit(new Runnable() {
            public void run() {
                // do work
            }
        });
        try {
            future.get(120 * 1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            logger.error("InterruptedException while notifyTransactionStateChangeListeners()", e);
            future.cancel(true);
        } catch (ExecutionException e) {
            logger.error("ExecutionException", e);
        } catch (TimeoutException e) {
            logger.error("TimeoutException", e);
            future.cancel(true);
        }
    }
});

Are there any other solutions?

You don't need an ExecutorService just to run a single thread one time like that. You can create a FutureTask instead which gives you the same benefits without the overhead.

FutureTask<T> future = new FutureTask<T>(callable);
Thread thread = new Thread(future);
thread.start();
try {
    future.get(120 * 1000, TimeUnit.MILLISECONDS);
} ...

The callable in the above snippet would be your task. If you have a Runnable (as you do in your above code block) you can turn it into a Callable via:

Callable callable = Executors.callable(runnable, null);

So, to summarize, your code could change to:

backgroundExecutor.execute(new Runnable() {
    public void run() {

        Runnable myRunnable = new Runnable() {
            public void run() {
                // do work
            }
        } 

        Callable callable = Executors.callable(myRunnable, null);

        FutureTask<T> future = new FutureTask<T>(callable);
        Thread thread = new Thread(future);
        thread.start();

        try {
            future.get(120 * 1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            logger.error("InterruptedException while notifyTransactionStateChangeListeners()", e);
            future.cancel(true);
        } catch (ExecutionException e) {
            logger.error("ExecutionException", e);
        } catch (TimeoutException e) {
            logger.error("TimeoutException", e);
            future.cancel(true);
        } 
    }
});

You don't need a finally to shut down the executor. Though you might still want a finally to clean up any other resources.

You can use Executor Service along with CompletableFuture. CompletableFuture runAsync accepts Runnable and ExecutorService Arguments.

final ExecutorService workerExecutor = Executors.newCachedThreadExecutor();

void queueTask(TaskId taskId) {
        workerExecutor.submit(() -> processTaskAsync(taskId));
    }

private void processTaskAsync(TaskId taskId) {
        CompletableFuture.runAsync(() -> processTask(taskId), this.workerExecutor)
                .whenComplete((ok, error) -> {
                    if (error != null) {
                        log.error("Exception while processing task", error);
                    } else {
                        log.info("finished post processing for task id {}", taskId.getValue());
                    }
                });
}

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