【问题标题】:Java Equivalent of C# async/await?Java 等价于 C# async/await?
【发布时间】:2013-05-08 12:02:37
【问题描述】:

我是一名普通的 C# 开发人员,但偶尔会使用 Java 开发应用程序。我想知道是否有任何 Java 等效于 C# async/await? 简而言之,什么是 java 等价物:

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();
    var urlContents = await client.GetStringAsync("http://msdn.microsoft.com");
    return urlContents.Length;
}

【问题讨论】:

  • 为什么这样会很好:Callbacks as our Generations’ Go To Statement by Miguel de Icaza。
  • Java目前的解决方案是不处理前缀为async的实际值,而是使用FutureObservable值。
  • 没有等价物。而且很痛。还有一项缺少的功能,您需要复杂的解决方法和库来实现,而无法达到与这两个简单单词相同的效果。
  • @pieroxy no async 不是多线程,python 早在 async 之前就有了多线程。多线程使用操作系统线程和它们之间的操作系统时间片。异步使用单个操作系统线程,当任务到达必须停止的逻辑点(例如网络读取)时,应用程序负责切换任务。异步的全部意义在于它在任务阻塞时重用同一个线程来执行其他任务。缺少线程纯粹是 JavaScript 的问题。
  • @pieroxy 仍然没有。即使在 c# 中,核心概念也保持不变,并且不是语法糖 (as described by jon skeet)。当然,多个线程可以从同一个池中执行任务,但这并不意味着异步是一种线程模型。异步很难理解,很多人不理解。在所有支持异步的语言中; await 可以挂起任务的整个调用栈,在同一个线程上执行不同的东西。在 java 中,你只是不能暂停堆栈并在以后恢复它。

标签: c# java


【解决方案1】:

不,在 Java 中没有任何等效的 async/await - 甚至在 v5 之前的 C# 中。

在幕后构建状态机是一项相当复杂的语言功能。

Java 中对异步/并发的语言 支持相对较少,但java.util.concurrent 包包含很多有用的。 (不完全等同于任务并行库,但最接近它。)

【讨论】:

  • @user960567:不,我的意思是它是一个语言特性——它不能单独放在库中。我认为至少 Java 8 没有任何等效的计划。
  • @user960567:您需要区分您使用的 C# 版本和您使用的 .NET 版本。 async/await 是一个 language 功能 - 它是在 C# 5 中引入的。是的,您可以使用 Microsoft.Bcl.Async 来使用面向 .NET 4 的 async/await,但您仍然必须使用一个 C# 5 编译器。
  • @rozar:不,不是。异步已经有多种选择——但 RxJava 并没有像 C# 那样改变 语言。我不反对 Rx,但它与 C# 5 中的异步不同。
  • @DtechNet:嗯,有很多 JVM 机器是异步的,是的……这与支持异步的实际语言特性非常不同。 (在 async/await 之前,.NET 中也有很多异步......但是 async/await 使得利用它更容易。)
  • @Aarkon:我认为除非有明确的 language 支持,否则答案仍然是正确的。让调度变得更简单的不仅仅是库的问题 - C# 编译器构建状态机的整个方式在这里很重要。
【解决方案2】:

await 在异步操作完成时使用延续来执行附加代码 (client.GetStringAsync(...))。

因此,作为最接近的近似值,我将使用基于 CompletableFuture&lt;T&gt;(Java 8 等效于 .net Task&lt;TResult&gt;)的解决方案来异步处理 Http 请求。

于 2016 年 5 月 25 日更新至 AsyncHttpClient v.2,于 2016 年 4 月 13 日发布:

所以Java 8相当于AccessTheWebAsync()的OP示例如下:

CompletableFuture<Integer> AccessTheWebAsync()
{
    AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient();
    return asyncHttpClient
       .prepareGet("http://msdn.microsoft.com")
       .execute()
       .toCompletableFuture()
       .thenApply(Response::getResponseBody)
       .thenApply(String::length);
}

此用法取自How do I get a CompletableFuture from an Async Http Client request?的答案 根据 2016 年 4 月 13 日发布的AsyncHttpClient 第 2 版中提供的新 API,它已经对 CompletableFuture&lt;T&gt; 提供了内在支持。

使用 AsyncHttpClient 版本 1 的原始答案:

为此,我们有两种可能的方法:

  • 第一个使用非阻塞 IO,我称之为AccessTheWebAsyncNio。然而,因为AsyncCompletionHandler 是一个抽象类(而不是函数式接口),我们不能将 lambda 作为参数传递。因此,由于匿名类的语法,它会导致不可避免的冗长。但是,此解决方案最接近给定 C# 示例的执行流程

  • 第二个稍微不那么冗长,但它会提交一个新的Task,最终将阻塞f.get() 上的一个线程,直到响应完成。

第一种方法,更详细但非阻塞:

static CompletableFuture<Integer> AccessTheWebAsyncNio(){
    final AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
    final CompletableFuture<Integer> promise = new CompletableFuture<>();
    asyncHttpClient
        .prepareGet("https://msdn.microsoft.com")
        .execute(new AsyncCompletionHandler<Response>(){
            @Override
            public Response onCompleted(Response resp) throws Exception {
                promise.complete(resp.getResponseBody().length());
                return resp;
            }
        });
    return promise;
}

第二种方法不那么冗长但阻塞了一个线程:

static CompletableFuture<Integer> AccessTheWebAsync(){
    try(AsyncHttpClient asyncHttpClient = new AsyncHttpClient()){
        Future<Response> f = asyncHttpClient
            .prepareGet("https://msdn.microsoft.com")
            .execute();
        return CompletableFuture.supplyAsync(
            () -> return f.join().getResponseBody().length());
    }
}

【讨论】:

  • 其实这也相当于快乐流。它不包括处理异常,finally 和其他。包含它们会使代码更复杂,更容易出错。
  • 这不是续集。这个例子漏掉了async/await的真正目的,就是释放当前线程去执行其他的事情,然后在响应到来后在当前线程上继续执行这个方法。 (这要么是 UI 线程响应所必需的,要么是为了减少内存使用量。)这个例子所做的是一个普通的阻塞线程同步,加上一些回调。
  • @AleksandrDubinsky 当您指出回调可能不会在调用者线程上运行时,我同意您的看法。你说的对。我不同意阻止线程。我对 UPDATED on 25-05-2016 的更新答案是非阻塞的。
  • .... 这个示例正是 C# 在执行异步操作时更易于编写和阅读的原因。这只是 Java 的痛苦。
【解决方案3】:

查看ea-async,它通过重写 Java 字节码来很好地模拟 async/await。根据他们的自述:“它深受 .NET CLR 上的 Async-Await 的启发”

【讨论】:

  • 有人在生产中使用它吗?
  • 好像EA有,我觉得他们不会把钱花在不适合制作的东西上。
  • 花钱买东西,然后决定不适合生产是很正常的;这是学习的唯一途径。可以在生产中不使用 java 代理设置的情况下使用它;这应该会降低一点(github.com/electronicarts/ea-async)。
【解决方案4】:

asyncawait 是语法糖。 async 和 await 的本质是状态机。编译器会将您的 async/await 代码转换为状态机。

同时,为了让 async/await 在实际项目中真正可行,我们需要大量的异步 I/O 库函数。对于 C#,大多数原始同步 I/O 函数都有一个替代的异步版本。我们需要这些 Async 函数的原因是因为在大多数情况下,您自己的 async/await 代码将归结为一些库 Async 方法。

C# 中的异步版本库函数有点像 Java 中的 AsynchronousChannel 概念。例如,我们有 AsynchronousFileChannel.read ,它可以在读取操作完成后返回 Future 或执行回调。但这并不完全相同。所有 C# 异步函数都返回任务(类似于 Future,但比 Future 更强大)。

假设 Java 确实支持 async/await,我们编写如下代码:

public static async Future<Byte> readFirstByteAsync(String filePath) {
    Path path = Paths.get(filePath);
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);

    ByteBuffer buffer = ByteBuffer.allocate(100_000);
    await channel.read(buffer, 0, buffer, this);
    return buffer.get(0);
}

然后我会想象编译器会将原始的 async/await 代码转换成这样的:

public static Future<Byte> readFirstByteAsync(String filePath) {

    CompletableFuture<Byte> result = new CompletableFuture<Byte>();

    AsyncHandler ah = new AsyncHandler(result, filePath);

    ah.completed(null, null);

    return result;
}

这是 AsyncHandler 的实现:

class AsyncHandler implements CompletionHandler<Integer, ByteBuffer>
{
    CompletableFuture<Byte> future;
    int state;
    String filePath;

    public AsyncHandler(CompletableFuture<Byte> future, String filePath)
    {
        this.future = future;
        this.state = 0;
        this.filePath = filePath;
    }

    @Override
    public void completed(Integer arg0, ByteBuffer arg1) {
        try {
            if (state == 0) {
                state = 1;
                Path path = Paths.get(filePath);
                AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);

                ByteBuffer buffer = ByteBuffer.allocate(100_000);
                channel.read(buffer, 0, buffer, this);
                return;
            } else {
                Byte ret = arg1.get(0);
                future.complete(ret);
            }

        } catch (Exception e) {
            future.completeExceptionally(e);
        }
    }

    @Override
    public void failed(Throwable arg0, ByteBuffer arg1) {
        future.completeExceptionally(arg0);
    }
}

【讨论】:

  • 合成糖?你知道如何围绕异步代码包装异常,并围绕异步代码循环吗?
  • 类也是语法糖。编译器会完全自动地为您创建通常手动编写的所有树和函数指针列表。这些函数/方法也是语法糖。它们会自动生成您通常会手动编写的所有 goto,作为真正的程序员。汇编器也是语法糖。真正的程序员手动编写机器代码并手动将其移植到所有目标架构。
  • 考虑一下,计算机本身只是 l4m3 n00bz 的语法糖。真正的程序员将微型集成电路焊接到木板上,然后用金线将它们连接起来,因为电路板是语法糖,就像批量生产、鞋子或食物一样。
  • 没错,将需要维护、理解和逐步执行的代码量减少几个数量级的语法糖是如此时髦。
【解决方案5】:

在语言级别上,Java 中没有 C# async/await 等价物。一个称为Fibers aka cooperative threads aka lightweight threads 的概念可能是一个有趣的替代方案。您可以找到为纤程提供支持的 Java 库。

实现 Fibers 的 Java 库

您可以阅读this article (from Quasar) 了解有关纤维的精彩介绍。它涵盖了线程是什么,如何在 JVM 上实现纤程,并包含一些 Quasar 特定的代码。

【讨论】:

  • C# 中的 async/await 不是 Fiber。它只是通过注册回调在 Promise(Task 类)上使用延续的编译器魔法。
  • @UltimaWeapon 那么你认为什么是纤维?
  • @AleksandrDubinsky 其中一个例子是 goroutine。
  • @UltimaWeapon 我正在寻找解释。
  • @AleksandrDubinsky 我懒得解释了。如果你真的想知道你可以搜索关于 goroutine 的文章。
【解决方案6】:

如前所述,没有直接的等价物,但可以通过 Java 字节码修改创建非常接近的近似值(对于类似 async/await 的指令和底层延续实现)。

我现在正在开发一个在 JavaFlow continuation 库之上实现 async/await 的项目,请查看 https://github.com/vsilaev/java-async-await

尚未创建 Maven mojo,但您可以使用提供的 Java 代理运行示例。这是 async/await 代码的样子:

public class AsyncAwaitNioFileChannelDemo {

public static void main(final String[] argv) throws Exception {

    ...
    final AsyncAwaitNioFileChannelDemo demo = new AsyncAwaitNioFileChannelDemo();
    final CompletionStage<String> result = demo.processFile("./.project");
    System.out.println("Returned to caller " + LocalTime.now());
    ...
}


public @async CompletionStage<String> processFile(final String fileName) throws IOException {
    final Path path = Paths.get(new File(fileName).toURI());
    try (
            final AsyncFileChannel file = new AsyncFileChannel(
                path, Collections.singleton(StandardOpenOption.READ), null
            );              
            final FileLock lock = await(file.lockAll(true))
        ) {

        System.out.println("In process, shared lock: " + lock);
        final ByteBuffer buffer = ByteBuffer.allocateDirect((int)file.size());

        await( file.read(buffer, 0L) );
        System.out.println("In process, bytes read: " + buffer);
        buffer.rewind();

        final String result = processBytes(buffer);

        return asyncResult(result);

    } catch (final IOException ex) {
        ex.printStackTrace(System.out);
        throw ex;
    }
}

@async 是将方法标记为异步可执行的注释,await() 是使用延续等待 CompletableFuture 的函数,调用“return asyncResult(someValue)”是最终确定关联的 CompletableFuture/Continuation

与 C# 一样,控制流被保留,异常处理可以以常规方式完成(尝试/捕获就像在顺序执行的代码中一样)

【讨论】:

    【解决方案7】:

    Java 本身没有等效功能,但存在提供类似功能的第三方库,例如Kilim

    【讨论】:

    • 我认为这个库与 async/await 的作用没有任何关系。
    【解决方案8】:

    Java 没有直接等效于 C# 语言功能的 async/await,但是对于 async/await 试图解决的问题有不同的方法。它被称为项目Loom,它将为高吞吐量并发提供虚拟线程。它将在 OpenJDK 的某些未来版本中提供。

    这种方法也解决了 async/await 的“colored function problem”。

    类似的功能也可以在 Golang (goroutines) 中找到。

    【讨论】:

    • 我很期待这个。我正在学习 Java,来自 Node.js 和 Go,这很痛苦。 Node.js 有 async/await(彩色函数问题从来没有困扰过我),而 Go 有 goroutines,所以你只需在生成的数百万个廉价 goroutines 中编写阻塞代码。 Java 似乎两者都没有,需要我使用 java.util.concurrent 或 RxJava 之类的东西来使我的代码非阻塞。如果我对 Project Loom 的理解是正确的,它将使 Java 中的异步编码变得像 Go 一样简单。您只需编写阻塞代码,而不必担心耗尽能够执行的“线程”。
    • @MattWelke 是的,这是正确的理解。
    【解决方案9】:

    首先,了解什么是异步/等待。它是单线程 GUI 应用程序或高效服务器在单个线程上运行多个“纤程”或“协同例程”或“轻量级线程”的一种方式。

    如果您可以使用普通线程,那么 Java 等效项是 ExecutorService.submitFuture.get。这将阻塞直到任务完成,并返回结果。同时,其他线程也可以工作。

    如果您想从纤维之类的东西中受益,则需要容器中的支持(我的意思是在 GUI 事件循环或服务器 HTTP 请求处理程序中),或者通过编写自己的支持。

    例如,Servlet 3.0 提供异步处理。 JavaFX 提供javafx.concurrent.Task。但是,这些没有语言功能的优雅。它们通过普通回调工作。

    【讨论】:

    • 这是一篇文章引用重新启动此答案的第一段 //start quote 对于客户端应用程序,例如 Windows 应用商店、Windows 桌面和 Windows Phone 应用程序,异步的主要好处是响应性。这些类型的应用程序主要使用异步来保持 UI 响应。对于服务器应用程序,异步的主要好处是可扩展性。 msdn.microsoft.com/en-us/magazine/dn802603.aspx
    【解决方案10】:

    Java 没有任何原生功能可以让您像 async/await 关键字那样执行此操作,但如果您真的想要的话,您可以使用 CountDownLatch。您可以然后通过传递它来模仿 async/await(至少在 Java7 中)。这是 Android 单元测试中的常见做法,我们必须进行异步调用(通常是由处理程序发布的可运行对象),然后等待结果(倒计时)。

    在您的应用程序中使用它而不是在您的测试中是 不是 我推荐的。这将是非常粗制滥造的,因为 CountDownLatch 取决于您在正确的位置有效地倒数正确的次数。

    【讨论】:

      【解决方案11】:

      我制作并发布了 Java async/await 库。 https://github.com/stofu1234/kamaitachi

      这个库不需要编译器扩展,用Java实现无栈IO处理。

          async Task<int> AccessTheWebAsync(){ 
              HttpClient client= new HttpClient();
              var urlContents= await client.GetStringAsync("http://msdn.microsoft.com");
             return urlContents.Length;
          }
      

         ↓

          //LikeWebApplicationTester.java
          BlockingQueue<Integer> AccessTheWebAsync() {
             HttpClient client = new HttpClient();
             return awaiter.await(
                  () -> client.GetStringAsync("http://msdn.microsoft.com"),
                  urlContents -> {
                      return urlContents.length();
                  });
          }
          public void doget(){
              BlockingQueue<Integer> lengthQueue=AccessTheWebAsync();
              awaiter.awaitVoid(()->lengthQueue.take(),
                  length->{
                      System.out.println("Length:"+length);
                  }
                  );
          }
      

      【讨论】:

      • 你将如何处理 String x = await f(); if(x == "Facebook") { LongAsyncFuncToRunFacebook();} else {LongAsyncFuncToRunSomethingElse();} AfterIfElseRunSomethingNewJustForFun();
      【解决方案12】:

      不幸的是,Java 没有 async/await 等价物。最接近的可能是来自 Guava 的 ListenableFuture 和侦听器链,但是对于涉及多个异步调用的情况编写仍然非常麻烦,因为嵌套级别会很快增长。

      如果您可以在 JVM 之上使用不同的语言,幸运的是 Scala 中有 async/await,它是直接的 C# async/await 等价物,具有几乎相同的语法和语义: https://github.com/scala/async/

      请注意,尽管此功能需要 C# 中非常高级的编译器支持,但在 Scala 中,由于 Scala 中非常强大的宏系统,它可以作为库添加,因此甚至可以添加到旧版本的 Scala 中,例如 2.10。此外,Scala 与 Java 类兼容,因此您可以在 Scala 中编写异步代码,然后从 Java 中调用它。

      还有另一个类似的项目称为 Akka Dataflow http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html,它使用不同的措辞,但在概念上非常相似,但是使用定界延续而不是宏实现(因此它适用于更旧的 Scala 版本,如 2.9)。

      【讨论】:

        【解决方案13】:

        AsynHelper Java 库包含一组用于此类异步调用(和等待)的实用程序类/方法。

        如果希望异步运行一组方法调用或代码块,它包括一个有用的辅助方法 AsyncTask.submitTasks,如下面的 sn-p。

        AsyncTask.submitTasks(
            () -> getMethodParam1(arg1, arg2),
            () -> getMethodParam2(arg2, arg3)
            () -> getMethodParam3(arg3, arg4),
            () -> {
                     //Some other code to run asynchronously
                  }
            );
        

        如果希望等到所有异步代码运行完毕,可以使用 AsyncTask.submitTasksAndWait 变体。

        此外,如果希望从每个异步方法调用或代码块中获取返回值,则可以使用 AsyncSupplier.submitSuppliers 以便可以通过以下方式获得结果方法返回的结果供应商数组。以下是示例 sn-p:

        Supplier<Object>[] resultSuppliers = 
           AsyncSupplier.submitSuppliers(
             () -> getMethodParam1(arg1, arg2),
             () -> getMethodParam2(arg3, arg4),
             () -> getMethodParam3(arg5, arg6)
           );
        
        Object a = resultSuppliers[0].get();
        Object b = resultSuppliers[1].get();
        Object c = resultSuppliers[2].get();
        
        myBigMethod(a,b,c);
        

        如果每个方法的返回类型不同,请使用下面的sn-p。

        Supplier<String> aResultSupplier = AsyncSupplier.submitSupplier(() -> getMethodParam1(arg1, arg2));
        Supplier<Integer> bResultSupplier = AsyncSupplier.submitSupplier(() -> getMethodParam2(arg3, arg4));
        Supplier<Object> cResultSupplier = AsyncSupplier.submitSupplier(() -> getMethodParam3(arg5, arg6));
        
        myBigMethod(aResultSupplier.get(), bResultSupplier.get(), cResultSupplier.get());
        

        异步方法调用/代码块的结果也可以在同一线程或不同线程的不同代码点获得,如下面的sn-p。

        AsyncSupplier.submitSupplierForSingleAccess(() -> getMethodParam1(arg1, arg2), "a");
        AsyncSupplier.submitSupplierForSingleAccess(() -> getMethodParam2(arg3, arg4), "b");
        AsyncSupplier.submitSupplierForSingleAccess(() -> getMethodParam3(arg5, arg6), "c");
        
        
        //Following can be in the same thread or a different thread
        Optional<String> aResult = AsyncSupplier.waitAndGetFromSupplier(String.class, "a");
        Optional<Integer> bResult = AsyncSupplier.waitAndGetFromSupplier(Integer.class, "b");
        Optional<Object> cResult = AsyncSupplier.waitAndGetFromSupplier(Object.class, "c");
        
         myBigMethod(aResult.get(),bResult.get(),cResult.get());
        

        【讨论】:

          【解决方案14】:

          如果您只是在使用与 java 中的 async/await 模拟相同效果的干净代码之后,并且不介意在完成之前阻塞调用它的线程,例如在测试中,您可以使用类似的东西这段代码:

          interface Async {
              void run(Runnable handler);
          }
          
          static void await(Async async) throws InterruptedException {
          
              final CountDownLatch countDownLatch = new CountDownLatch(1);
              async.run(new Runnable() {
          
                  @Override
                  public void run() {
                      countDownLatch.countDown();
                  }
              });
              countDownLatch.await(YOUR_TIMEOUT_VALUE_IN_SECONDS, TimeUnit.SECONDS);
          }
          
              await(new Async() {
                  @Override
                  public void run(final Runnable handler) {
                      yourAsyncMethod(new CompletionHandler() {
          
                          @Override
                          public void completion() {
                              handler.run();
                          }
                      });
                  }
              });
          

          【讨论】:

            【解决方案15】:

            EA 开发了一个“等价的”await:https://github.com/electronicarts/ea-async。参考Java示例代码:

            import static com.ea.async.Async.await;
            import static java.util.concurrent.CompletableFuture.completedFuture;
            
            public class Store
            {
                public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
                {
                    if(!await(bank.decrement(cost))) {
                        return completedFuture(false);
                    }
                    await(inventory.giveItem(itemTypeId));
                    return completedFuture(true);
                }
            }
            

            【讨论】:

              【解决方案16】:

              我已经开发了一个库JAsync 来执行此操作。 它今天刚刚发布。 它使开发人员的异步编程体验尽可能接近通常的同步编程,包括代码风格和调试。 这是一个例子。

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

              在调试模式下,你可以像同步代码一样看到所有变量。

              这个项目的另一个好处是,它是目前仍然活跃的少数此类项目之一。刚刚发布,所以潜力很大

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-09-10
                • 1970-01-01
                • 2014-01-27
                • 1970-01-01
                • 2010-10-01
                相关资源
                最近更新 更多