Java等效于C#异步/等待?


151

我是一名普通的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;
}

7
为什么这么好:Miguel de Icaza的回调作为我们这代人的“致辞”。
andrewdotn

Java当前的解决方案是不处理带有前缀的实际值async,而是使用FutureObservable值。
SD

1
没有对等的东西。而且很痛。您还需要一个缺少的功能,即复杂的解决方法和库,而无法达到与这两个简单的单词相同的效果。
spyro

值得注意的是,很长一段时间以来,Java设计人员一直试图使Java字节码向后兼容,仅对库和Syntatic-sugar的现有功能进行更改。看到泛型不存储运行时类型信息并且lambder被实现为实现接口的对象这一事实。异步/等待将需要对字节码进行很大的更改,因此我不希望很快在Java中看到它。
菲利普·库林

Answers:


140

不,在Java中-甚至在v5之前的C#中,没有任何等效的异步/等待方式。

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

Java中对异步/并发的语言支持相对较少,但是该java.util.concurrent软件包包含许多与此相关的有用。(不完全等效于任务并行库,但是最接近它。)


15
@ user960567:不,我的意思是它是一种语言功能-不能仅放在库中。我认为至少没有为Java 8安排任何等效的计划。
乔恩·斯基特

10
@ user960567:您需要区分使用的C#版本和使用的.NET版本。异步/等待是一种语言功能-它是C#5中引入的。是的,您可以使用Microsoft.Bcl.Async来使用针对.NET 4的异步/等待,但是您仍必须使用C#5编译器。
乔恩·斯基特

5
@rozar:不,不是真的。异步已经有多种选择-但是RxJava不会像C#那样改变语言。我不反对的Rx什么,但它不是在C#5同样的事情异步
乔恩斯基特

11
@DtechNet:嗯,有很多异步的JVM机器,是的……这与支持异步的实际语言功能有很大的不同。(在异步/等待之前,.NET中也有很多异步...但是异步/等待使利用它变得容易得多。)
Jon Skeet 2015年

1
@Aarkon:我认为除非有明确的语言支持,否则答案仍然是正确的。简化调度的不仅仅是库问题-C#编译器构建状态机的整个方法在这里很重要。
乔恩·斯基特

41

所述await使用的延续当异步操作完成(以执行额外的代码client.GetStringAsync(...))。

因此,作为最接近的近似,我将使用CompletableFuture<T>(基于Java 8相当于.net的Task<TResult>)解决方案来异步处理Http请求。

2016年5月25日更新到2016年3月13日发布的AsyncHttpClient v.2:

因此,相当于OP示例的Java 8 AccessTheWebAsync()如下:

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

此用法来自“ 如何从异步Http客户端请求中获取CompletableFuture”的答案 并根据2016年3月13日发布的AsyncHttpClient版本2中提供的新API 进行了说明CompletableFuture<T>

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

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

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

  • 第二个稍微不那么冗长,但是它将提交一个新的Task,该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());
    }
}

1
实际上,这相当于开心的一刻。最后,它不涉及处理异常。包含它们将使代码更加复杂并且更容易出错。
haimb

1
这不是延续。此示例错过了async / await的真正目的,即释放当前线程以执行其他操作,然后在响应到达后继续在当前线程上执行此方法。(这是UI线程响应或减少内存使用所必需的。)本示例执行的操作是简单地阻止线程同步以及一些回调。
Aleksandr Dubinsky

1
@AleksandrDubinsky当您指出回调可能不会在调用者线程上运行时,我同意您的看法。你是对的。我不同意阻塞线程。我在2016年5月25日更新的UPDATED答案是无阻塞的。
米格尔·甘博阿

1
....这个示例正是做异步工作时C#如此简单地编写和读取的原因。这只是Java的痛苦。
spyro

29

看看ea-async,它做了Java字节码重写以很好地模拟async / await。根据他们的自述文件:“它在很大程度上受到.NET CLR上Async-Await的启发”


8
有人在生产中使用它吗?
王先生从隔壁

1
看来EA确实有,我不认为他们会花钱购买不适合生产的产品。
BrunoJCM

1
把钱花在某物上然后决定不适合生产是很正常的。那是唯一的学习方法。在生产中无需Java代理设置就可以使用它。这应该降低一些标准(github.com/electronicarts/ea-async)。
thoredge '18

16

异步等待是语法糖。异步和等待的本质是状态机。编译器会将您的异步/等待代码转换为状态机。

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

C#中的Async版本库函数有点像Java中的AsynchronousChannel概念。例如,我们有AsynchronousFileChannel.read,它可以返回Future或在读取操作完成后执行回调。但这并不完全相同。所有C#异步函数都返回Task(类似于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);
}

然后我可以想象编译器会将原始的异步/等待代码转换为如下形式:

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);
    }
}

15
糖吗 您是否有关于如何在异步代码周围包装异常以及在异步代码周围循环的想法?
Akash Kava

39
类也是语法糖。编译器会自动为您自动创建通常通常手动编写的所有树和函数指针列表。这些功能/方法也是语法糖。他们会自动生成您通常作为真正的程序员手工编写的所有命令。汇编器也是语法糖。真正的程序员手动编写机器代码并将其手动移植到所有目标体系结构。
yeoman

32
顺便说一句,计算机本身只是l4m3 n00bz的语法糖。真正的程序员将它们微小的集成电路焊接到木板上,并用金线连接它们,因为电路板是语法糖,就像批量生产,制造鞋子或食物一样。
yeoman

14

在语言级别上,Java中没有等效的C#async / await。一种称为纤维(又称为协作线程,又称为轻量级线程)的概念可能是一个有趣的替代方法。您可以找到提供对光纤支持的Java库。

实施Fiber的Java库

您可以阅读这篇文章(来自Quasar),以获得有关纤维的不错介绍。它涵盖了什么是线程,如何在JVM上实现光纤以及具有Quasar特定的代码。


10
C#中的异步/等待不是光纤。它只是Task通过注册回调在Promise(类)上使用延续的编译器魔术。
UltimaWeapon'6

1
@UltimaWeapon那么,您认为纤维是什么?
Aleksandr Dubinsky '18

@AleksandrDubinsky示例之一是goroutine。
UltimaWeapon '18

1
@UltimaWeapon我在寻找解释。
亚历山大·杜宾斯基

@AleksandrDubinsky我很懒惰地解释它。如果您真的想知道,可以在goroutine的幕后搜索有关的文章。
UltimaWeapon

8

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

我现在正在研究一个在JavaFlow连续库之上实现async / await的项目,请检查 https://github.com/vsilaev/java-async-await

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

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#一样,控制流得以保留,异常处理可以按常规方式进行(尝试/捕获类似于顺序执行的代码)



5

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

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

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

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


2
这是重新引用此答案的第一段的文章引用//开始引用对于客户端应用程序(例如Windows Store,Windows桌面和Windows Phone应用程序),异步的主要好处是响应能力。这些类型的应用程序主要使用异步来保持UI响应。对于服务器应用程序,异步的主要好处是可伸缩性。 msdn.microsoft.com/zh-CN/magazine/dn802603.aspx
granadaCoder

3

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

但是,我建议在应用程序内部使用它而不是测试。这将非常伪劣,因为CountDownLatch取决于您有效地在正确的地方正确地计数了正确的次数。


3

我制作并发布了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);
            }
            );
    }

1

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

如果您可以在JVM之上使用其他语言,那么幸运的是,Scala中有async / await,它是直接C#async / await,等效于几乎相同的语法和语义:https : //github.com/scala/异步/

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

还有另一个名为Akka Dataflow的类似项目http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html,它使用不同的措词,但在概念上非常相似,但是使用定界的延续而不是宏来实现(因此,它甚至可以与2.9等较旧的Scala版本一起使用)。


1

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

该方法还解决了异步/等待所具有的“ 有色功能问题 ”。

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


0

如果您只是使用干净的代码来模拟与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();
                }
            });
        }
    });

0

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

如果希望异步运行一组方法调用或代码块,则它包括一个有用的帮助器方法AsyncTask .submitTasks,如下面的代码片段所示。

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

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

同样,如果希望从每个异步方法调用或代码块中获取返回值,则可以使用AsyncSupplier .submitSuppliers,以便可以从该方法返回的结果供应商数组中获取结果。以下是示例代码段:

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);

如果每种方法的返回类型不同,请使用以下类型的代码段。

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());

异步方法调用/代码块的结果也可以在同一线程或不同线程中的不同代码点获得,如下面的代码片段所示。

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());
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.