Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Java have no async/await?

Using async/await it is possible to code asynchronous functions in an imperative style. This can greatly facilitate asynchronous programming. After it was first introduced in C#, it was adopted by many languages such as JavaScript, Python, and Kotlin.

EA Async is a library that adds async/await like functionality to Java. The library abstracts away the complexity of working with CompletableFutures.

But why has async/await neither been added to Java SE, nor are there any plans to add it in the future?

like image 431
Stefan Feuerhahn Avatar asked Sep 24 '19 07:09

Stefan Feuerhahn


People also ask

Does Java have await?

Java has unfortunately no equivalent of async/await. The closest you can get is probably with ListenableFuture from Guava and listener chaining, but it would be still very cumbersome to write for cases involving multiple asynchronous calls, as the nesting level would very quickly grow.

Does Java support async programming?

There are multiple ways to do Async programming in Java, starting from Thread, Runnable, Callable<T>, Future<T> (and its extended ScheduledFuture<T>), CompletableFuture<T>, and of course, ExecutorService and ForkJoinPool. Java concurrency is the functionality that enables asynchronous programming in Java.

Is Java sync or async?

So in the context of Java, we have to Create a new thread and invoke the callback method inside that thread. The callback function may be invoked from a thread but is not a requirement. A Callback may also start a new thread, thus making themselves asynchronous.

Is it OK to not await async?

If you forget to use await while calling an async function, the function starts executing. This means that await is not required for executing the function. The async function will return a promise, which you can use later.


3 Answers

The short answer is that the designers of Java try to eliminate the need for asynchronous methods instead of facilitating their use.

According to Ron Pressler's talk asynchronous programming using CompletableFuture causes three main problems.

  1. branching or looping over the results of asynchronous method calls is not possible
  2. stacktraces cannot be used to identify the source of errors, profiling becomes impossible
  3. it is viral: all methods that do asynchronous calls have to be asynchronous as well, i.e. synchronous and asynchronous worlds don't mix

While async/await solves the first problem it can only partially solve the second problem and does not solve the third problem at all (e.g. all methods in C# doing an await have to be marked as async).

But why is asynchronous programming needed at all? Only to prevent the blocking of threads, because threads are expensive. Thus instead of introducing async/await in Java, in project Loom Java designers are working on virtual threads (aka fibers/lightweight threads) which will aim to significantly reduce the cost of threads and thus eliminate the need of asynchronous programming. This would make all three problems above also obsolete.

like image 65
Stefan Feuerhahn Avatar answered Oct 18 '22 03:10

Stefan Feuerhahn


Better late than never!!! Java is 10+ years late in trying to come up with lighter weight units of execution which can be executed in parallel. As a side note, Project loom also aims to expose in Java 'delimited continuation' which, I believe is nothing more than good old 'yield' keyword of C# (again almost 20 years late!!)

Java does recognize the need for solving the bigger problem solved by asyn await (or actually Tasks in C# which is the big idea. Async Await is more of a syntactical sugar. Highly significant improvement, but still not a necessity to solve the actual problem of OS mapped Threads being heavier than desired).

Look at the proposal for project loom here: https://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html and navigate to last section 'Other Approaches'. You will see why Java does not want to introduce async/await.

Having said this, I don't really agree with the reasoning being provided. Neither in this proposal nor in Stephan's answer.

First let us diagnose Stephan's answer

  1. async await solves point 1 mentioned there. (Stephan also acknowledges it further down the answer)
  2. It is extra work for sure on the part of the framework and tools but not at all on the part of the programmers. Even with async await, .Net debuggers are pretty good in this aspect.
  3. This I only partially agree with. Whole purpose of async await is to elegantly mix asynchronous world with synchronous constructs. But yes, you either need to declare the caller also as async or deal directly with Task in the caller routine. However, project loom will not solve it either in a meaningful way. To fully benefit from the light weight virtual threads, even the caller routine must be getting executed on a virtual thread. Otherwise what's the benefit? You will end up blocking an OS backed thread!!! Hence even virtual threads need to be 'viral' in the code. On the contrary, it will be easier in Java to not notice that the routine you are calling is async and will block the calling thread (which will be concerning if the calling routine is itself not executing on a virtual thread). Async keyword in C# makes the intent very clear and forces you to decide (it is possible in C# to block as well if you want by asking for Task.Result. Most of the time the calling routine can just as easily be async itself).

Stephan is right when he says async programming is needed to prevent blocking of (OS) threads as (OS) threads are expensive. And that's precisely the whole reason why virtual threads (or C# tasks) are needed. You should be able to 'block' on these tasks without losing your sleep. Offcourse to not lose the sleep, either the calling routine itself should be a task or blocking should be on non-blocking IO, with framework being smart enough to not block the calling thread in that case (power of continuation).

C# supports this and proposed Java feature aims to support this. According to the proposed Java api, blocking on virtual thread will require calling vThread.join() method in Java. How is it really more beneficial than calling await workDoneByVThread()?

Now let us look at project loom proposal reasoning

Continuations and fibers dominate async/await in the sense that async/await is easily implemented with continuations (in fact, it can be implemented with a weak form of delimited continuations known as stackless continuations, that don't capture an entire call-stack but only the local context of a single subroutine), but not vice-versa

I don't simply understand this statement. If someone does, please let me know in the comments.

For me, async/await are implemented using continuations and as far as stack trace is concerned, since the fibres/virtual threads/tasks are within the virtual machine, it must be possible to manage that aspect. In-fact .net tools do manage that.

While async/await makes code simpler and gives it the appearance of normal, sequential code, like asynchronous code it still requires significant changes to existing code, explicit support in libraries, and does not interoperate well with synchronous code

I have already covered this. Not making significant changes to existing code and no explicit support in libraries will actually mean not using this feature effectively. Until and unless Java is aiming to transparently transform all the threads to virtual threads, which it can't and isn't, this statement does not make sense to me.

As a core idea, I find no real difference between Java virtual threads and C# tasks. To the point that project loom is also aiming for work-stealing scheduler as default, same as the scheduler used by .Net by default (https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler?view=net-5.0, scroll to last remarks section ). Only debate it seems is on what syntax should be adopted to consume these.

C# adopted

  1. A distinct class and interface as compared to existing threads
  2. Very helpful syntactical sugar for marrying async with sync

Java is aiming for:

  1. Same familiar interface of Java Thread
  2. No special constructs apart from try-with-resources support for ExecutorService so that the result for submitted tasks/virtual threads can be automatically waited for (thus blocking the calling thread, virtual/non-virtual).

IMHO, Java's choices are worse than those of C#. Having a separate interface and class actually makes it very clear that the behavior is a lot different. Retaining same old interface can lead to subtle bugs when a programmer does not realize that she is now dealing with something different or when a library implementation changes to take advantage of the new constructs but ends up blocking the calling (non-virtual) thread.

Also no special language syntax means that reading async code will remain difficult to understand and reason about (I don't know why Java thinks programmers are in love with Java's Thread syntax and they will be thrilled to know that instead of writing sync looking code they will be using the lovely Thread class)

Heck, even Javascript now has async await (with all its 'single-threadedness').

like image 24
Amit Mittal Avatar answered Oct 18 '22 03:10

Amit Mittal


I release a new project JAsync implement async-await fashion in java which use Reactor as its low level framework. It is in the alpha stage. I need more suggest and test case. This project makes the developer's asynchronous programming experience as close as possible to the usual synchronous programming, including both coding and debugging. I think my project solves point 1 mentioned by Stephan.

Here is an example:

@RestController
@RequestMapping("/employees")
public class MyRestController {
    @Inject
    private EmployeeRepository employeeRepository;
    @Inject
    private SalaryRepository salaryRepository;

    // The standard JAsync async method must be annotated with the Async annotation, and return a JPromise object.
    @Async()
    private JPromise<Double> _getEmployeeTotalSalaryByDepartment(String department) {
        double money = 0.0;
        // A Mono object can be transformed to the JPromise object. So we get a Mono object first.
        Mono<List<Employee>> empsMono = employeeRepository.findEmployeeByDepartment(department);
        // Transformed the Mono object to the JPromise object.
        JPromise<List<Employee>> empsPromise = Promises.from(empsMono);
        // Use await just like es and c# to get the value of the JPromise without blocking the current thread.
        for (Employee employee : empsPromise.await()) {
            // The method findSalaryByEmployee also return a Mono object. We transform it to the JPromise just like above. And then await to get the result.
            Salary salary = Promises.from(salaryRepository.findSalaryByEmployee(employee.id)).await();
            money += salary.total;
        }
        // The async method must return a JPromise object, so we use just method to wrap the result to a JPromise.
        return JAsync.just(money);
    }

    // This is a normal webflux method.
    @GetMapping("/{department}/salary")
    public Mono<Double> getEmployeeTotalSalaryByDepartment(@PathVariable String department) { 
        // Use unwrap method to transform the JPromise object back to the Mono object.
        return _getEmployeeTotalSalaryByDepartment(department).unwrap(Mono.class);
    }
}

In addition to coding, JAsync also greatly improves the debugging experience of async code. When debugging, you can see all variables in the monitor window just like when debugging normal code. I will try my best to solve point 2 mentioned by Stephan.

enter image description here

For point 3, I think it is not a big problem. Async/Await is popular in c# and es even if it is not satisfied with it.

like image 1
vipcxj Avatar answered Oct 18 '22 05:10

vipcxj