CompleteFuture failed to execute asynchronously but returned success

CompleteFuture failed to execute asynchronously but returned success

1. Custom thread pool

@Configuration
public class ThreadPoolConfig {<!-- -->

    public static ThreadPoolExecutor getThreadPoolExecutor() {<!-- -->
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        return new ThreadPoolExecutor(
                availableProcessors,
                availableProcessors,
                0L,
                TimeUnit. MILLISECONDS,
                new LinkedBlockingQueue<>(9999),
                new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build(),
                new ThreadPoolExecutor. CallerRunsPolicy());
    }

}

Write three simple asynchronous tasks and create an exception when the third task is executed

public static final ThreadPoolExecutor threadPoolExecutor = ThreadPoolConfig.getThreadPoolExecutor();

/**
 * Asynchronous task orchestration
 */
@GetMapping("/asyncTaskArrange")
public ResponseData<Object> asyncTaskArrange() {<!-- -->
    try {<!-- -->
        CompletableFuture.runAsync(() -> log.info("Task 1"), threadPoolExecutor)
                .thenRunAsync(() -> log.info("Task 2"), threadPoolExecutor)
                .thenRunAsync(() -> {<!-- -->
                    log.info("Task 3");
                    int i = 1 / 0;
                }, threadPoolExecutor)
                .exceptionally(e -> {<!-- -->
                    log.error("Exception message: " + e.getMessage(), e);
                    throw new BusinessException(e. getMessage());
                });

        return new ResponseData<>(StatusCodeEnum.SUCCESS_CODE.getStatusCode(), "The operation was successful");
    } catch (Exception e) {<!-- -->
        log.error("Program exception information: " + e.getMessage(), e);
        return new ResponseData<>(StatusCodeEnum.ERROR_CODE.getStatusCode(), "Operation failed:" + e.getMessage());
    }
}

program execution result

The console prints the exception information, and the asynchronous task itself catches and throws the exception information, but the try-catch that decides whether the program executes successfully or fails does not catch the exception information, so it returns successfully.

The solution is to let the asynchronous task generate a CompleteFuture, and call the get() method or join(). Note: get() in the asynchronous task is blocked, and a timeout period needs to be added when using it.

/**
 * Asynchronous task orchestration
 */
@GetMapping("/asyncTaskArrange")
public ResponseData<Object> asyncTaskArrange() {<!-- -->
    try {<!-- -->
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> log.info("Task 1"), threadPoolExecutor)
                .thenRunAsync(() -> log.info("Task 2"), threadPoolExecutor)
                .thenRunAsync(() -> {<!-- -->
                    log.info("Task 3");
                    int i = 1 / 0;
                }, threadPoolExecutor)
                .exceptionally(e -> {<!-- -->
                    log.error("Exception message: " + e.getMessage(), e);
                    throw new BusinessException(e. getMessage());
                });
        // future.get(10, TimeUnit.SECONDS);
        future. join();

        return new ResponseData<>(StatusCodeEnum.SUCCESS_CODE.getStatusCode(), "The operation was successful");
    } catch (Exception e) {<!-- -->
        log.error("Program exception information: " + e.getMessage(), e);
        return new ResponseData<>(StatusCodeEnum.ERROR_CODE.getStatusCode(), "Operation failed:" + e.getMessage());
    }
}

Execute the program again to return the correct execution result of the interface

Asynchronous task exception information processing method

handle

handle needs to be called after an asynchronous method that may have an exception. e indicates the exception information of the previous asynchronous task. If it is null, it means that the execution is successful. Otherwise, an exception is found and an exception information needs to be thrown.

/**
* handle handles asynchronous exceptions
*/
@GetMapping("/asyncHandle")
public ResponseData<Object> asyncHandle() {<!-- -->
try {<!-- -->
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> log.info("Task 1"), threadPoolExecutor)
.thenRunAsync(() -> log.info("Task 2"), threadPoolExecutor)
.thenRunAsync(() -> {<!-- -->
log.info("Task 3");
int i = 1 / 0;
}, threadPoolExecutor)
.handle((result, e) -> {<!-- -->
if (e != null) {<!-- -->
log.error("Exception message: " + e.getMessage(), e);
throw new BusinessException(e. getMessage());
} else {<!-- -->
return null;
}
});
// future.get(10, TimeUnit.SECONDS);
future. join();

return new ResponseData<>(StatusCodeEnum.SUCCESS_CODE.getStatusCode(), "The operation was successful");
} catch (Exception e) {<!-- -->
log.error("Program exception information: " + e.getMessage(), e);
return new ResponseData<>(StatusCodeEnum.ERROR_CODE.getStatusCode(), "Operation failed:" + e.getMessage());
}
}


Now change the position of the handle, put it after the asynchronous task that does not have abnormal methods, and test again, and the exception can still be caught, so the handle will capture the entire orchestrated asynchronous task chain.

/**
* handle handles asynchronous exceptions
*/
@GetMapping("/asyncHandle")
public ResponseData<Object> asyncHandle() {<!-- -->
try {<!-- -->
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> log.info("Task 1"), threadPoolExecutor)
.thenRunAsync(() -> log.info("Task 2"), threadPoolExecutor)
.handle((result, e) -> {<!-- -->
if (e != null) {<!-- -->
log.error("Exception message: " + e.getMessage(), e);
throw new BusinessException(e. getMessage());
} else {<!-- -->
return null;
}
})
.thenRunAsync(() -> {<!-- -->
log.info("Task 3");
int i = 1 / 0;
}, threadPoolExecutor);
// future.get(10, TimeUnit.SECONDS);
future. join();

return new ResponseData<>(StatusCodeEnum.SUCCESS_CODE.getStatusCode(), "The operation was successful");
} catch (Exception e) {<!-- -->
log.error("Program exception information: " + e.getMessage(), e);
return new ResponseData<>(StatusCodeEnum.ERROR_CODE.getStatusCode(), "Operation failed:" + e.getMessage());
}
}

whenComplete

The callback method after the execution of the whenComplete task is completed, also judges whether there is an exception in the last asynchronous task through the e in the input parameter.

/**
* handle handles asynchronous exceptions
*/
@GetMapping("/asyncWhenComplete")
public ResponseData<Object> asyncWhenComplete() {<!-- -->
try {<!-- -->
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> log.info("Task 1"), threadPoolExecutor)
.thenRunAsync(() -> log.info("Task 2"), threadPoolExecutor)
.thenRunAsync(() -> {<!-- -->
log.info("Task 3");
int i = 1 / 0;
}, threadPoolExecutor)
.whenComplete((result , e) -> {<!-- -->
if (e != null) {<!-- -->
log.error("Exception message: " + e.getMessage(), e);
throw new BusinessException(e. getMessage());
}
});
// future.get(10, TimeUnit.SECONDS);
future. join();

return new ResponseData<>(StatusCodeEnum.SUCCESS_CODE.getStatusCode(), "The operation was successful");
} catch (Exception e) {<!-- -->
log.error("Program exception information: " + e.getMessage(), e);
return new ResponseData<>(StatusCodeEnum.ERROR_CODE.getStatusCode(), "Operation failed:" + e.getMessage());
}
}

exceptionally

If the orchestrated asynchronous task executes abnormally, it will execute exceptionally, and if it executes normally, it will return the result of normal execution.