How to interrupt underlying execution of CompletableFuture

前端 未结 4 1608
自闭症患者
自闭症患者 2020-12-29 05:04

I know that CompletableFuture design does not control its execution with interruptions, but I suppose some of you might have this problem. CompletableFutu

相关标签:
4条回答
  • 2020-12-29 05:10

    What about?

    /** @return {@link CompletableFuture} which when cancelled will interrupt the supplier
     */
    public static <T> CompletableFuture<T> supplyAsyncInterruptibly(Supplier<T> supplier, Executor executor) {
        return produceInterruptibleCompletableFuture((s) -> CompletableFuture.supplyAsync(s, executor), supplier);
    }
    
    // in case we want to do the same for similar methods later
    private static <T> CompletableFuture<T> produceInterruptibleCompletableFuture(
            Function<Supplier<T>,CompletableFuture<T>> completableFutureAsyncSupplier, Supplier<T> action) {
        FutureTask<T> task = new FutureTask<>(action::get);
        return addCancellationAction(completableFutureAsyncSupplier.apply(asSupplier(task)), () ->
                task.cancel(true));
    }
    
    /** Ensures the specified action is executed if the given {@link CompletableFuture} is cancelled.
     */
    public static <T> CompletableFuture<T> addCancellationAction(CompletableFuture<T> completableFuture,
                                                                 @NonNull Runnable onCancellationAction) {
        completableFuture.whenComplete((result, throwable) -> {
            if (completableFuture.isCancelled()) {
                onCancellationAction.run();
            }
        });
        return completableFuture;  // return original CompletableFuture
    }
    
    /** @return {@link Supplier} wrapper for the given {@link RunnableFuture} which calls {@link RunnableFuture#run()}
     *          followed by {@link RunnableFuture#get()}.
     */
    public static <T> Supplier<T> asSupplier(RunnableFuture<T> futureTask) throws CompletionException {
        return () -> {
            try {
                futureTask.run();
                try {
                    return futureTask.get();
                } catch (ExecutionException e) {  // unwrap ExecutionExceptions
                    final Throwable cause = e.getCause();
                    throw (cause != null) ? cause : e;
                }
            } catch (CompletionException e) {
                throw e;
            } catch (Throwable t) {
                throw new CompletionException(t);
            }
        };
    }
    
    0 讨论(0)
  • 2020-12-29 05:26

    A CompletableFuture is not related to the asynchronous action that may eventually complete it.

    Since (unlike FutureTask) this class has no direct control over the computation that causes it to be completed, cancellation is treated as just another form of exceptional completion. Method cancel has the same effect as completeExceptionally(new CancellationException()).

    There may not even be a separate thread working on completing it (there may even be many threads working on it). Even if there is, there's no link from a CompletableFuture to any thread that has a reference to it.

    As such, there's nothing you can do through CompletableFuture to interrupt any thread that may be running some task that will complete it. You'll have to write your own logic which tracks any Thread instances which acquire a reference to the CompletableFuture with the intention to complete it.


    Here's an example of the type of execution I think you could get away with.

    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(1);
        CompletableFuture<String> completable = new CompletableFuture<>();
        Future<?> future = service.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    if (Thread.interrupted()) {
                        return; // remains uncompleted
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        return; // remains uncompleted
                    }
                }
                completable.complete("done");
            }
        });
    
        Thread.sleep(2000);
    
        // not atomic across the two
        boolean cancelled = future.cancel(true);
        if (cancelled)
            completable.cancel(true); // may not have been cancelled if execution has already completed
        if (completable.isCancelled()) {
            System.out.println("cancelled");
        } else if (completable.isCompletedExceptionally()) {
            System.out.println("exception");
        } else {
            System.out.println("success");
        }
        service.shutdown();
    }
    

    This assumes that the task being executed is setup to handle interruptions correctly.

    0 讨论(0)
  • 2020-12-29 05:26

    What about this?

    public static <T> CompletableFuture<T> supplyAsync(final Supplier<T> supplier) {
    
        final ExecutorService executorService = Executors.newFixedThreadPool(1);
    
        final CompletableFuture<T> cf = new CompletableFuture<T>() {
            @Override
            public boolean complete(T value) {
                if (isDone()) {
                    return false;
                }
                executorService.shutdownNow();
                return super.complete(value);
            }
    
            @Override
            public boolean completeExceptionally(Throwable ex) {
                if (isDone()) {
                    return false;
                }
                executorService.shutdownNow();
                return super.completeExceptionally(ex);
            }
        };
    
        // submit task
        executorService.submit(() -> {
            try {
                cf.complete(supplier.get());
            } catch (Throwable ex) {
                cf.completeExceptionally(ex);
            }
        });
    
        return cf;
    }
    

    Simple Test:

        CompletableFuture<String> cf = supplyAsync(() -> {
            try {
                Thread.sleep(1000L);
            } catch (Exception e) {
                System.out.println("got interrupted");
                return "got interrupted";
            }
            System.out.println("normal complete");
            return "normal complete";
        });
    
        cf.complete("manual complete");
        System.out.println(cf.get());
    

    I don't like the idea of having to create an Executor service every time, but maybe you can find a way to reuse the ForkJoinPool.

    0 讨论(0)
  • 2020-12-29 05:28

    If you use

    cf.get();
    

    instead of

    cf.join();
    

    The thread waiting on the completion can be interrupted. This bit me in the a**, so I'm just putting it out there. You'd then need to propagate this interruption further / use cf.cancel(...) to really finish the execution.

    0 讨论(0)
提交回复
热议问题