为什么Java没有异步/等待?

时间:2019-09-24 07:37:14

标签: java asynchronous async-await completable-future

使用async / await可以以命令式样式编写异步函数。这可以极大地促进异步编程。自从C#首次引入以来,它已被JavaScript,Python和Kotlin等许多语言所采用。

EA Async是一个向Java添加类似于异步/等待功能的库。该库消除了使用CompletableFutures的复杂性。

但是为什么既未将async / await添加到Java SE中,又未计划在将来添加它?

2 个答案:

答案 0 :(得分:11)

简短的答案是Java的设计者试图消除对异步方法的需求,而不是促进它们的使用。

根据罗恩·普勒斯勒(Ron Pressler)的talk使用CompletableFuture进行异步编程会导致三个主要问题。

  1. 无法分支或遍历异步方法调用的结果
  2. stacktraces无法用于识别错误源,无法进行分析
  3. 这是病毒式的:所有执行异步调用的方法也必须是异步的,即同步世界和异步世界不要混在一起

虽然异步/等待解决了第一个问题,但它只能部分解决第二个问题,而根本解决不了第三个问题(例如,C#中所有执行 的方法都必须标记为< em> async )。

但是为什么根本需要异步编程?只是为了防止线程阻塞,因为线程很昂贵。因此,不是在Java中引入异步/等待,而是在Loom项目中,Java设计人员正在研究光纤(也称为轻量级线程),其目的是显着降低线程的成本,从而消除对异步编程的需求。这将使以上所有三个问题也过时。

答案 1 :(得分:1)

迟到总比不到好!!! Java 在尝试提出可以并行执行的更轻量级的执行单元方面晚了 10 多年。附带说明一下,Project Loom 还旨在在 Java 中公开“分隔继续”,我认为这只不过是 C# 的老式“yield”关键字(又晚了将近 20 年!!)

Java 确实认识到需要解决由 asyn await(或实际上 C# 中的任务,这是一个伟大的想法。Async Await 更像是一种语法糖。非常显着的改进,但仍然不是解决操作系统映射线程的实际问题比预期的要重)。

在此处查看项目织机的提案:https://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html 并导航到最后一节“其他方法”。你会明白为什么 Java 不想引入 async/await。

话虽如此,我并不完全同意所提供的推理。无论是在这个提案中,还是在 Stephan 的回答中。

首先让我们诊断斯蒂芬的答案

  1. async await 解决了那里提到的第 1 点。 (Stephan 也在答案中进一步承认了这一点)
  2. 对于框架和工具来说肯定是额外的工作,但对于程序员来说则完全不是。即使使用异步等待,.Net 调试器在这方面也相当不错。
  3. 我只是部分同意这一点。 async await 的全部目的是优雅地将异步世界与同步构造混合在一起。但是,是的,您要么需要将调用方也声明为异步,要么直接在调用方例程中处理 Task。但是,项目织机也不会以有意义的方式解决它。为了充分受益于轻量级虚拟线程,甚至调用例程也必须在虚拟线程上执行。不然有什么好处?你最终会阻塞一个操作系统支持的线程!!!因此,即使是虚拟线程也需要在代码中成为“病毒式”。相反,在 Java 中更容易注意到您正在调用的例程是异步的,并且会阻塞调用线程(如果调用例程本身不在虚拟线程上执行,这将令人担忧)。 C# 中的 Async 关键字使意图非常明确并迫使您做出决定(如果您希望,也可以在 C# 中通过请求 Task.Result 来阻止。大多数情况下,调用例程本身也很容易异步)。

Stephan 是对的,他说需要异步编程来防止 (OS) 线程阻塞,因为 (OS) 线程很昂贵。这正是需要虚拟线程(或 C# 任务)的全部原因。您应该能够“阻止”这些任务而不会失眠。当然为了不失去睡眠,调用例程本身应该是一个任务,或者阻塞应该在非阻塞 IO 上,框架足够聪明,在这种情况下不会阻塞调用线程(延续的力量)。

C# 支持这一点,并且提议的 Java 特性旨在支持这一点。 根据提议的 Java api,在虚拟线程上阻塞将需要在 Java 中调用 vThread.join() 方法。 它比调用 await workDoneByVThread() 有什么好处?

现在让我们来看看项目织机提案的推理

<块引用>

延续和纤程在 async/await 中占主导地位,因为 async/await 很容易用延续来实现(事实上,它可以用一种弱形式的分隔延续来实现,称为无堆栈延续,它不会捕获整个调用-stack 但只是单个子程序的本地上下文),反之亦然

我并不简单地理解这句话。如果有人这样做,请在评论中告诉我。

对我来说,async/await 是使用 continuation 实现的,就堆栈跟踪而言,由于光纤/虚拟线程/任务在虚拟机内,因此必须可以管理该方面。事实上,.net 工具确实可以做到这一点。

<块引用>

虽然 async/await 使代码更简单,并赋予它正常、顺序代码的外观,但与异步代码一样,它仍然需要对现有代码进行重大更改、库中的显式支持,并且不能与同步代码很好地互操作

我已经介绍过了。不对现有代码进行重大更改并且库中没有明确支持实际上意味着不能有效地使用此功能。直到并且除非 Java 旨在将所有线程透明地转换为虚拟线程(它不能也不能),否则此声明对我来说没有意义。

作为一个核心思想,我发现 Java 虚拟线程和 C# 任务之间没有真正的区别。就这一点而言,project Loom 也针对默认情况下窃取工作的调度程序,与 .Net 默认使用的调度程序相同(https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler?view=net-5.0,滚动到最后的备注部分)。 似乎只有争论应该采用什么语法来消费这些。

采用C#

  1. 与现有线程相比具有独特的类和接口
  2. 非常有用的语法糖,用于将异步与同步结合

Java 的目标是:

  1. 同样熟悉的 Java Thread 界面
  2. 除了对 ExecutorService 的 try-with-resources 支持外,没有其他特殊结构,因此可以自动等待提交的任务/虚拟线程的结果(从而阻塞调用线程、虚拟/非虚拟)。

恕我直言,Java 的选择比 C# 的差。拥有单独的接口和类实际上可以很清楚地表明行为有很大不同。当程序员没有意识到她现在正在处理不同的事情时,或者当库实现更改以利用新构造但最终阻塞调用(非虚拟)线程时,保留相同的旧接口可能会导致细微的错误。< /p>

也没有特殊的语言语法意味着阅读异步代码仍然难以理解和推理(我不知道为什么 Java 认为程序员喜欢 Java 的 Thread 语法,他们会很高兴知道而不是编写同步寻找他们将使用可爱的 Thread 类的代码)

哎呀,即使是 Javascript 现在也有异步等待(以及它所有的“单线程”)。

相关问题