Java эквивалент C # async / await?

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;
}
user960567
источник
7
Почему это было бы неплохо: обратные вызовы как заявление Мигеля де Иказы наших поколений .
Andrewdotn
Текущее решение в Java, чтобы не иметь дело с реальными значениями с префиксом async, но использование Futureили Observableзначений вместо этого.
SD
1
Нет аналога И это больно. Еще одна недостающая особенность, для которой вам нужны сложные обходные пути и библиотеки, чтобы при этом не достичь того же эффекта, что и у этих двух простых слов.
Spyro
Стоит отметить, что долгое время дизайнеры Java пытались поддерживать обратную совместимость байт-кода Java только с изменениями в библиотеках и Syntatic-sugar вокруг существующих функций. См тот факт , что дженерики не хранить информацию о типе времени выполнения и lambders реализованы как объект , реализующий интерфейс . Для async / await потребуются очень большие изменения в байт-коде, и поэтому я не ожидаю увидеть его в Java в ближайшее время.
Филипп Коулинг

Ответы:

141

Нет, нет эквивалента async / await в Java - или даже в C # до v5.

Это довольно сложная языковая функция для создания конечного автомата за кулисами.

Существует относительно небольшая языковая поддержка асинхронности / параллелизма в Java, но java.util.concurrentпакет содержит много полезных классов вокруг этого. (Не совсем эквивалентный параллельной библиотеке задач, но наиболее близкий к ней.)

Джон Скит
источник
15
@ user960567: Нет, я хочу сказать, что это языковая функция - ее нельзя поместить только в библиотеки. Я не верю, что для Java 8 запланирован какой-то эквивалент.
Джон Скит
10
@ user960567: Необходимо различать версию C #, которую вы используете, и версию .NET, которую вы используете. async / await - это языковая функция - она ​​была представлена ​​в C # 5. Да, вы можете использовать Microsoft.Bcl.Async для использования async / await с таргетингом на .NET 4, но вы все равно должны использовать компилятор C # 5.
Джон Скит
5
@rozar: нет, не совсем. Уже есть несколько вариантов асинхронности, но RxJava не меняет язык так, как это делал C #. Я ничего не имею против Rx, но это не то же самое, что асинхронность в C # 5.
Джон Скит,
11
@DtechNet: Ну, есть много машин JVM, которые являются асинхронными, да ... это очень отличается от того, что есть реальные возможности языка, поддерживающие асинхронность. (До появления async / await в .NET также было много асинхронности ... но async / await значительно упрощает использование этого преимущества.)
Джон Скит,
1
@ Aarkon: я бы сказал, что если нет явной языковой поддержки, ответ по-прежнему правильный. Дело не только в библиотеках, которые упрощают планирование - здесь важен весь способ, которым компилятор C # создает конечный автомат.
Джон Скит
41

awaitИспользует продолжение , чтобы выполнить дополнительный код , когда асинхронная операция завершается ( client.GetStringAsync(...)).

Таким образом, в качестве наиболее близкого приближения я бы использовал решение на основе CompletableFuture<T>(эквивалент Java. 8 Task<TResult>) для асинхронной обработки запроса Http.

ОБНОВЛЕНО 25-05-2016 до версии AsyncHttpClient v.2, выпущенной 13 апреля 2016 года:

Таким образом, Java 8, эквивалентный примеру OP, AccessTheWebAsync()выглядит следующим образом:

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

Это использование было взято из ответа на Как получить CompletableFuture из запроса Async Http Client? и который соответствует новому API, предоставленному во второй версии AsyncHttpClient, выпущенной 13 апреля 2016 года, которая уже имеет встроенную поддержку CompletableFuture<T>.

Исходный ответ с использованием версии 1 AsyncHttpClient:

Для этого у нас есть два возможных подхода:

  • первый использует неблокирующий ввод-вывод, и я это называю AccessTheWebAsyncNio. Тем не менее, поскольку AsyncCompletionHandlerэто абстрактный класс (а не функциональный интерфейс), мы не можем передать лямбду в качестве аргумента. Таким образом, это неизбежно связано с синтаксисом анонимных классов. Однако это решение наиболее близко к потоку выполнения данного примера C # .

  • второй вариант немного менее многословен, однако он отправит новую задачу, которая в конечном итоге заблокирует поток 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
На самом деле, это эквивалент счастливого потока. Это не распространяется на обработку исключений, наконец и других. Включение их сделает код намного более сложным и более подверженным ошибкам.
подняться
1
Это не продолжение. В этом примере пропущена реальная цель async / await, которая заключается в освобождении текущего потока для выполнения других задач, а затем для продолжения выполнения этого метода в текущем потоке после получения ответа. (Это необходимо либо для того, чтобы поток пользовательского интерфейса реагировал, либо для уменьшения использования памяти.) В этом примере выполняется простая блокировка синхронизации потока, а также некоторые обратные вызовы.
Александр Дубинский
1
@AleksandrDubinsky Я согласен с вами, когда вы указываете, что обратный вызов может не выполняться в потоке вызывающего. Ты прав. Я не согласен по поводу блокировки потока. Мой обновленный ответ ОБНОВЛЕНО 25-05-2016 не является блокирующим.
Мигель Гамбоа
1
.... и этот пример как раз и является причиной того, что C # намного проще писать и читать при выполнении асинхронных операций. Это просто боль в Java.
Spyro
29

Проверьте ea-async, который выполняет переписывание байт-кода Java для имитации async / await довольно красиво. Согласно их readme: «Это вдохновлено Async-Await в .NET CLR»

Hafthor
источник
8
Кто-нибудь использует это в производстве?
Мистер Ван из Next Door
1
Кажется, что EA это делает, я не думаю, что они будут тратить деньги на то, что не подходит для производства.
BrunoJCM
1
Вполне нормально потратить деньги на что-то, а затем решить, что это не подходит для производства; это единственный способ учиться. Его можно использовать без настроек java-агента на производстве; это должно немного понизить планку ( github.com/electronicarts/ea-async ).
августа
16

async и await являются синтаксическими сахарами. Суть асинхронности и ожидания - конечный автомат. Компилятор преобразует ваш асинхронный / ожидающий код в конечный автомат.

В то же время, для того, чтобы async / await реально применялся в реальных проектах, нам необходимо иметь множество функций библиотеки ввода-вывода Async . Для C # большинство оригинальных синхронизированных функций ввода / вывода имеет альтернативную асинхронную версию. Причина, по которой нам нужны эти асинхронные функции, заключается в том, что в большинстве случаев ваш собственный асинхронный / ожидающий код сводится к некоторому библиотечному асинхронному методу.

Функции библиотеки версий Async в C # подобны концепции AsynchronousChannel в Java. Например, у нас есть AsynchronousFileChannel.read, который может либо вернуть Future, либо выполнить обратный вызов после завершения операции чтения. Но это не совсем то же самое. Все функции C # Async возвращают Задачи (аналогично 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);
    }
}
caisx25
источник
15
Синтетический сахар? Есть ли у вас идеи о том, как обернуть исключения вокруг асинхронного кода и циклы вокруг асинхронного кода?
Акаш Кава
39
Занятия тоже являются синтаксическим сахаром. Компилятор создает все деревья и списки указателей функций, которые вы обычно пишете вручную, полностью автоматически. Эти функции / методы также являются синтаксическим сахаром. Они автоматически генерируют все goto, которые вы обычно, будучи настоящим программистом, пишите вручную. Ассемблер тоже синтаксический сахар. Настоящие программисты вручную пишут машинный код и вручную переносят его на все целевые архитектуры.
Yeoman
32
Думая об этом, сами компьютеры являются просто синтаксическим сахаром для l4m3 n00bz. Настоящие программисты припаивают их крошечные интегральные схемы к деревянной доске и соединяют их с помощью золотой проволоки, потому что печатные платы являются синтаксическим сахаром, как массовое производство, обувь или еда.
Йомен
14

На уровне языка нет эквивалента C # async / await в Java. Концепция, известная как Fibers или кооперативные нити, или легкие нити, может быть интересной альтернативой. Вы можете найти библиотеки Java, обеспечивающие поддержку волокон.

Библиотеки Java, реализующие Fibers

Вы можете прочитать эту статью (от Quasar) для хорошего знакомства с волокнами. Он описывает, что такое потоки, как волокна могут быть реализованы в JVM, и содержит некоторый специфичный для Quasar код.

FabienB
источник
10
async / await в C # не является Fiber. Это просто магия компилятора, использующая продолжение в Promise ( Taskклассе) путем регистрации обратного вызова.
UltimaWeapon
1
@UltimaWeapon Итак, что вы считаете волокнами?
Александр Дубинский
@AleksandrDubinsky Одним из примеров является горутин.
UltimaWeapon
1
@ UltimaWeapon Я искал объяснение.
Александр Дубинский
@AleksandrDubinsky Мне лень это объяснять. Если вы действительно хотите знать, вы можете поискать статью под капотом goroutine.
UltimaWeapon
8

Как было упомянуто, прямого эквивалента нет, но очень близкое приближение можно было бы создать с помощью модификаций байт-кода Java (как для асинхронных / await-подобных инструкций, так и для реализации базовых продолжений).

Сейчас я работаю над проектом, который реализует async / await поверх библиотеки продолжения JavaFlow , пожалуйста, проверьте 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 #, поток управления сохраняется, и обработка исключений может выполняться обычным образом (try / catch, как в последовательно выполняемом коде)

Валерий Силаев
источник
6

Сама Java не имеет эквивалентных функций, но существуют сторонние библиотеки, которые предлагают аналогичные функции, например, Kilim .

Алексей Кайгородов
источник
3
Я не думаю, что эта библиотека имеет какое-либо отношение к тому, что делает async / await.
Натан
5

Во-первых, понять, что такое async / await. Это позволяет однопоточному приложению с графическим интерфейсом или эффективному серверу запускать несколько «волокон», «подпрограмм» или «облегченных потоков» в одном потоке.

Если вы согласны с использованием обычных потоков, то эквивалент Java есть ExecutorService.submitи Future.get. Это заблокирует, пока задача не завершится, и вернет результат. Между тем, другие темы могут делать работу.

Если вы хотите использовать преимущества чего-то вроде волокон, вам нужна поддержка в контейнере (я имею в виду в цикле событий GUI или в обработчике HTTP-запросов сервера) или при написании собственного.

Например, Servlet 3.0 предлагает асинхронную обработку. JavaFX предлагает javafx.concurrent.Task. Они не имеют элегантности языковых особенностей, хотя. Они работают через обычные обратные вызовы.

Александр Дубинский
источник
2
Вот цитата из статьи, которая перезапускает первый абзац этого ответа // начальная цитата. Для клиентских приложений, таких как Windows Store, Windows Desktop и Windows Phone, основным преимуществом асинхронности является оперативность. Эти типы приложений используют асинхронность в основном для обеспечения отзывчивости интерфейса. Для серверных приложений основным преимуществом асинхронности является масштабируемость. msdn.microsoft.com/en-us/magazine/dn802603.aspx
granadaCoder
3

В Java нет ничего такого, что позволяло бы вам делать это, например, ключевые слова async / await, но что вы можете сделать, если действительно хотите, это использовать CountDownLatch . Затем вы можете имитировать async / await, передавая это (по крайней мере, в Java7). Это обычная практика в модульном тестировании Android, когда мы должны сделать асинхронный вызов (обычно это исполняемый файл, отправленный обработчиком), а затем дождаться результата (обратный отсчет).

Однако использование этого в вашем приложении в отличие от вашего теста НЕ является тем, что я рекомендую. Это было бы крайне некачественно, поскольку CountDownLatch зависит от того, насколько эффективно вы отсчитываете нужное количество раз и в нужных местах.

stevebot
источник
3

Я делаю и выпускаю библиотеку Java async / await. https://github.com/stofu1234/kamaitachi

Эта библиотека не нуждается в расширении компилятора и реализует обработку ввода-вывода без стеков в Java.

    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);
            }
            );
    }
stofu
источник
1

К сожалению, Java не имеет эквивалента async / await. Самое близкое, что вы можете получить, это, вероятно, с ListenableFuture из Guava и цепочкой слушателей, но было бы все еще очень громоздко писать для случаев, связанных с несколькими асинхронными вызовами, так как уровень вложенности очень быстро рос.

Если вы согласны с использованием другого языка поверх 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, который использует другую формулировку, но концептуально очень похож, однако реализован с использованием продолжения с разделителями, а не макросов. (поэтому он работает даже с более старыми версиями Scala, такими как 2.9).

Петр Колачковский
источник
1

Java не имеет прямого эквивалента возможности языка C # под названием async / await, однако существует другой подход к проблеме, которую пытается решить async / await. Он называется проектом Loom , который будет предоставлять виртуальные потоки для высокопроизводительного параллелизма. Он будет доступен в некоторых будущих версиях OpenJDK.

Этот подход также решает " проблему цветных функций ", которая есть у async / await.

Подобная особенность также может быть найдена в Golang ( goroutines ).

Мартин Обратил
источник
0

Если вам нужен только чистый код, который имитирует тот же эффект, что и async / await в java, и не возражаете против блокировки потока, к которому он вызывается, до его завершения, например, в тесте, вы можете использовать что-то вроде этого кода:

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

Библиотека Java AsynHelper включает в себя набор служебных классов / методов для таких асинхронных вызовов (и ожидания).

Если необходимо выполнить набор вызовов методов или блоков кода асинхронно, он включает полезный вспомогательный метод 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());
Loganathan
источник