This is the 6th day of my participation in the August More Text Challenge

When multiple asynchronous tasks are completed, sometimes multiple tasks need to be executed in series, and sometimes the execution results of multiple tasks need to be combined, which requires subsequent operations after the completion of multiple tasks. In order to facilitate our handling of such cases, CompletableFuture also provides some methods for us to implement to merge multiple tasks.

ThenApply () is used

Three methods are provided for us to use. The parameters of this method have similar meanings with those of the previous two articles. We will not analyze them separately here, but put them together to see how to use them and their implementation principles and differences. Old rule, first paste source code.

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
    return uniApplyStage(null, fn);
}

public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn) {
    return uniApplyStage(asyncPool, fn);
}

public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) {
    return uniApplyStage(screenExecutor(executor), fn);
}
Copy the code

As can be seen from the above three methods, the internal execution calls the uniApplyStage method, but there are differences in the parameters of the thread pool. The differences of the thread pool are the same as those of the thread pool created by the asynchronous task. We will not analyze them in detail here. Function is passed into the method to declare the business code logic to be executed later. In Function, there is a parameter T, which represents the result returned after the last task is successfully executed, and fn represents the result data type of the current task, which is eventually mapped to the result data type in the CompletableFuture. Let’s look at how to use this method.

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            return 123;
        }, executor);
        CompletableFuture<Integer> fun = future.thenApplyAsync(integer -> {
            System.out.println(Thread.currentThread().getName());
            System.out.println("Execution Result of last task :" + integer);
            return 3333;
        },executor);
        System.out.println("Second task :" + fun.get());
Copy the code

Execution Result:

pool-1-thread-1
pool-1-thread-2Execution result of last task:123Second task:3333
Copy the code

As the result of execution, the second task is to open a new thread to execute its own task. ThenApply/thenApplyAsync/thenApplyAsync/thenApplyAsync/thenApplyAsync/thenApplyAsync

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            return 123;
        }, executor);
        CompletableFuture<Integer> fun = future.thenApply(integer -> {
            System.out.println(Thread.currentThread().getName());
            System.out.println("Execution Result of last task :" + integer);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 3333;
        });
        System.out.println("Second task :" + fun.get());
Copy the code

Execution Result:

pool-1-thread-1Main Result of the previous task:123Second task:3333
Copy the code

The result shows that the main thread is used to execute the tasks executed by thenApply.

The handle () is used

Since there are exceptions that need to be thrown when we run the code, there is no exception handling logic in the three methods of thenApply, so what should we do if we want to handle exceptions? The class provides handle() for us to use. Handle () works just like thenApply(), except that it returns an exception thrown by multiple preceding tasks in the method. In Handle (), we can either handle normal results or handle exceptions after catching exceptions. Handle also provides three methods for handling exception information. I will paste the specific three methods here without giving an example of how to use them

ThenAccept () is used

The above two sets of methods, thenApply() and handle(), require that the specific execution result be returned after the second task is completed, but we do not need to return the final result after the execution. The author must have thought of this point along the way in studying the principle of CompletableFuture. Provide us with methods to call, or say it is easy to use, at least one point is to consider the scenario is relatively rich, can meet our various complex business scenarios, from now we see only a part of them, well, no more nonsense, we continue to see. First on the source code, look at the back

public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
    return uniAcceptStage(null, action);
}

public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
    return uniAcceptStage(asyncPool, action);
}

public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
    return uniAcceptStage(screenExecutor(executor), action);
}
Copy the code

See the source of these three method, should know how to use at a glance, I won’t, for example, how to use here, and the difference of the above is no return value, at first glance many eagle-eyed children see that this method can’t handle abnormal happen on a task, it must be the author think, you don’t worry, as soon as posted on the exception handling method

ThenRun () is used

public CompletableFuture<Void> thenRun(Runnable action) {
    return uniRunStage(null, action);
}

public CompletableFuture<Void> thenRunAsync(Runnable action) {
    return uniRunStage(asyncPool, action);
}

public CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor) {
    return uniRunStage(screenExecutor(executor), action);
}
Copy the code

Read the source code of these three methods, should understand how to use, then go down to analyze it.

The above methods are serial execution. After the completion of the last task, the next task will be executed. Is there a method that can be combined by two people? The answer is definitely yes!!

The two tasks are executed on the same time

In the process of execution, some tasks need to be executed in serial and some tasks need to be executed in parallel. The serial execution functions above have already met our requirements, and the rest need to solve the problem of parallel execution, which can be divided into two scenarios:

  • One: after two parallel execution is completed or the execution results of two tasks are combined, and a value is returned at last
  • Another: two parallel tasks to execute a million, who finished the first, based on the results of who, and then complete the follow-up business processing, and finally return the value

The two tasks have been completed

ThenCombine () is used

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("1:" + Thread.currentThread().getName());
            return 123;
        }, executor);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("2:" + Thread.currentThread().getName());
            return 123;
        }, executor);
        CompletableFuture<Integer> future3 = future1.thenCombineAsync(future2, (f1, f2) -> {
            System.out.println("3:" + Thread.currentThread().getName());
            System.out.println("Result of task 1 :" + f1);
            System.out.println("Result of task 2 :" + f2);
            return 3;
        },executor);
        System.out.println("Final execution result :" + future3.get());
Copy the code

** Execution result:

1:pool-1-thread-1
2:pool-1-thread-2
3:pool-1-thread-31Results of five tasks:1232Results of five tasks:123Final execution result:3
Copy the code

As you can see from the results printed above, the step we just performed is equivalent to building three asynchronous asynchronous tasks, except that the third asynchronous task is executing the final task after the first and second asynchronous tasks have completed. Well, if you read my article carefully, you can probably guess what methods I want to write next, which are definitely methods that return no values and accept task exceptions.

ThenAcceptBoth () is used

ThenAcceptBoth () is used in a similar way to thenCombine(), when two tasks are completed, the results of both tasks are obtained for subsequent processing, except that thenAcceptBoth() does not return a value

RunAfterBoth () is used

The above two methods need to obtain the results returned after the completion of the first two tasks for subsequent operation processing, then there will definitely be when the two tasks are completed, do not want the results of the first two tasks, and there will be no value returned

The function for parallel processing of the above two tasks has been posted, but I did not find the current two tasks to execute the exception, how to obtain the information of the exception, anyway I did not find, estimated author wait until the later to provide it!! Let’s see if the completion of either task triggers the subsequent action.

Complete the two tasks randomly

Look at the two tasks complete the API, must be able to guess how to use any task after the completion of the method, I will post the specific method, it is too sleepy.

ApplyToEither () is used

After the two tasks are executed, the execution result of the first one is used, and the result is returned after the subsequent operations are completed

AcceptEither () is used

AcceptEither () is similar to applyToEither, except that it returns no value

RunAfterEither () is used

After the two tasks are completed, the one who completes them first performs the following operations without the execution results of the first two tasks

Well, the two tasks on here are about the same, this is not too much to analysis how to realize the logic inside the source code, more is to find according to different scenarios, using the method of how to mainly because the younger brother pretty good, on the inside of the source code analysis is not clear, be afraid perpetuated this misunderstanding, did not analyze the source code, and not all knowledge is to master principle, More often than not, it will work, and the implementation will be researched when you need to dig deeper. Now that I’m at the end of this blog post, I’m sure some people are wondering how these two tasks are enough for me, and what I should do if I have more than one task. Ha ha ha, old rules oh, please listen to the next breakdown.