Java-еквівалент C # async / чекає?


151

Я звичайний розробник C #, але час від часу розробляю програми на Java. Мені цікаво, чи є якийсь Java-еквівалент C # async / чекає? Простими словами, що таке еквівалент java:

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


Поточне рішення Java - не мати справу з фактичними значеннями з префіксом async, а використовувати Futureабо Observableзначення.
SD

1
Не існує еквівалента. І боляче. Ще одна відсутня функція, для якої потрібні складні обхідні шляхи та бібліотеки, не досягнувши жодного ефекту, як ці два простих слова.
шпигун

Варто відзначити, що тривалий час дизайнери Java намагалися підтримувати байт-код Java назад сумісним лише із змінами в бібліотеках та синтатичному цукрі навколо існуючих функцій. Подивіться на те, що дженерики не зберігають інформацію про тип часу виконання, а ламбдери реалізовані як об'єкт, що реалізує інтерфейс . async / await потребує дуже великих змін в байт-коді, щоб бути можливим, і тому я не сподівався побачити його в Java скоро.
Філіп Кулінг

Відповіді:


140

Ні, немає жодного еквівалента асинхронізації / очікування на Java - або навіть у C # перед v5.

Це досить складна мовна особливість побудови державної машини за лаштунками.

Мовна підтримка асинхронності / одночасності в Java є порівняно малою , але java.util.concurrentпакет містить багато корисних класів навколо цього. (Не зовсім рівнозначна бібліотеці паралельних завдань, але найближче наближення до неї.)


15
@ user960567: Ні, я можу сказати, що це мовна особливість - її не можна розміщувати виключно в бібліотеках. Я не вірю, що для Java 8 заплановано принаймні еквівалент.
Джон Скіт

10
@ user960567: Вам потрібно розрізняти версію C #, яку ви використовуєте, та версію .NET, яку ви використовуєте. async / await є мовною особливістю - вона була введена в C # 5. Так, ви можете використовувати Microsoft.Bcl.Async, щоб використовувати async / ждати націлювання .NET 4, але ви все ще повинні використовувати компілятор C # 5.
Джон Скіт

5
@rozar: Ні, не дуже. Існує вже кілька варіантів асинхронії - але RxJava не змінює мову так, як це робив C #. Я нічого не маю проти Rx, але це не те саме, що асинхронізація в C # 5.
Джон Скіт,

11
@DtechNet: Ну, є багато механізмів JVM, який є асинхронним, так ... це дуже відрізняється від того, що існують фактичні особливості мови, що підтримують асинхронність. (Було багато асинхронності в .NET, перш ніж async / ждати теж ..., але async / await робить це далеко простіше скористатися цим.)
Джон Скіт,

1
@Aarkon: Я б заперечував, що якщо немає явної мови підтримки , відповідь все-таки правильна. Тут не просто питання бібліотек спрощує планування - весь спосіб, коли компілятор C # будує державну машину, тут важливий.
Джон Скіт

41

awaitВикористовує продовження , щоб виконати додатковий код , коли асинхронна операція завершується ( client.GetStringAsync(...)).

Отже, як найбільш близьке наближення я б використав рішення CompletableFuture<T>(на Java 8, еквівалентне .net 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? і згідно з новим API, наданим у версії 2 AsyncHttpClient, випущеної 13 квітня 2016 року, яка вже має внутрішню підтримку CompletableFuture<T>.

Оригінальний відповідь, використовуючи версію 1 AsyncHttpClient:

Для цього у нас є два можливі підходи:

  • перший використовує неблокуючий IO, і я його називаю 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
Насправді, це еквівалент щасливого потоку. Нарешті, він не охоплює обробку винятків, нарешті та інших. Включення їх зробить код набагато складнішим і більш схильним до помилок.
haimb

1
Це не продовження. У цьому прикладі відсутня реальна мета async / await, яка полягає у звільненні поточного потоку для виконання інших речей, а потім продовженні виконання цього методу в поточному потоці після надходження відповіді. (Це потрібно або для того, щоб потік інтерфейсу користувача був чуйним, або для зменшення використання пам'яті.) Цей приклад - це звичайна блокування синхронізації потоку, а також деякі зворотні виклики.
Олександр Дубінський

1
@AleksandrDubinsky Я погоджуюся з вами, коли ви вказуєте, що зворотний дзвінок може не працювати на потоці виклику. Ти правий. Я не згоден з блокуванням потоку. Моя оновлена ​​відповідь UPDATED 25-05-2016 не блокує.
Мігель Гамбоа

1
.... і саме цей зразок є саме причиною того, чому C # набагато простіше писати і читати, роблячи асинхронні речі. Це просто біль на Яві.
шпигун

29

Перевірте ea-async, що робить перезапис байт-коду Java, щоб імітувати async / очікувати досить добре. За їх прочитанням: "Це сильно надихає Async-Await в .NET CLR"


8
Хтось використовує це у виробництві?
Містер Ванг із сусідніх дверей

1
Здається, що ЕА робить, я не думаю, що вони витратили б гроші на те, що не підходить для виробництва.
BrunoJCM

1
Цілком нормально витрачати гроші на щось, а потім вирішити, що це не підходить для виробництва; це єдиний спосіб вчитися. Використовувати його можна без налаштувань агента java у виробництві; що має трохи опустити планку ( github.com/electronicarts/ea-async ).
торедж

16

асинхроніка і очікування - синтаксичні цукри. Суть асинхронізації та очікування - державна машина. Компілятор перетворить ваш код асинхронізації / очікування в стан машини.

У той же час, для того, щоб асинхронізація / очікування справді практична у реальних проектах, нам потрібно мати багато функцій бібліотеки вводу / виводу Async . Для C #, більшість оригінальних синхронізованих функцій вводу / виводу має альтернативну версію Async. Причина, що нам потрібні ці функції Async, полягає в тому, що в більшості випадків ваш власний код асинхрон / очікування зводиться до якогось методу Async бібліотеки.

Функції бібліотеки версій Async в C # подібні до концепції AsynchronousChannel на Java. Наприклад, у нас є AsynchronousFileChannel.read, який може або повернути Майбутнє, або виконати зворотний виклик після завершення операції зчитування. Але це не зовсім так. Усі функції C # Async повертають завдання (подібні до майбутнього, але потужніші, ніж майбутні).

Отже, скажімо, Java підтримує async / wait, і ми пишемо такий код, як цей:

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

15
Синтатичний цукор? Чи маєте ви якесь уявлення про те, як обернути винятки навколо коду асинхронізації та циклу навколо коду async?
Акаш Кава

39
Класи теж є синтаксичним цукром. Компілятор створює всі дерева та списки функціональних покажчиків, які ви зазвичай пишете від руки повністю автоматично. Ці функції / методи також є синтаксичним цукром. Вони автоматично генерують усі gotos, які ви зазвичай, будучи справжніми програмістами, пишіть від руки. Асемблер - це також синтаксичний цукор. Справжні програмісти вручну записують машинний код і вручну переносять його на всі цільові архітектури.
yeoman

32
Думаючи про це, самі комп'ютери є просто синтаксичним цукром для l4m3 n00bz. Справжні програмісти припаюють їх крихітними інтегральними схемами до дерев’яної дошки та з'єднують їх із золотим дротом, оскільки плати є синтаксичним цукром, як і масове виробництво, взуття чи їжа.
yeoman

14

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

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

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


10
async / жди в C # - це не Волокно. Це просто магія компілятора, яка використовує продовження на Promise ( Taskклас), зареєструвавши зворотний виклик.
UltimaWeapon

1
@UltimaWeapon Отже, що ви вважаєте волокнами?
Олександр Дубінський

@AleksandrDubinsky Один із прикладів - goroutine.
UltimaWeapon

1
@UltimaWeapon я шукав пояснення.
Олександр Дубінський

@AleksandrDubinsky Мені лінь це пояснити. Якщо ви дійсно хочете знати, ви можете шукати статтю про кришку гороутена.
UltimaWeapon

8

Як вже було сказано, прямого еквівалента немає, але дуже близьке наближення могло б бути створене за допомогою модифікацій байт-коду Java (як для інструкцій, подібних до async / очікуючих, так і для основної реалізації продовження).

Зараз я працюю над проектом, який реалізує асинхронізацію / очікує поверх бібліотеки продовження 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 - це примітка, яка позначає метод як асинхронно виконуваний файл, wait () - це функція, яка чекає на CompletableFuture з використанням продовжень та виклик "повернути asyncResult (someValue)" - це те, що завершує асоційований CompletableFuture / Continuation

Як і у випадку зі C #, керуючий потік зберігається, а обробка винятків може здійснюватися регулярно (спробуйте вловлювати, як у послідовно виконаному коді)


6

Сама Java не має еквівалентних функцій, але існують сторонні бібліотеки, які пропонують аналогічні функції, наприклад, Kilim .


3
Я не думаю, що ця бібліотека не має нічого спільного з тим, що робить асинхрон / очікування.
Натан

5

Спочатку зрозумійте, що таке асинхроніка / очікування. Це спосіб для однопотокової програми GUI або ефективного сервера запускати кілька "волокон" або "спільних програм" або "легких ниток" на одному потоці.

Якщо ви все добре використовуєте звичайні потоки, то еквівалентом Java є ExecutorService.submitі Future.get. Це заблокує, поки завдання не буде виконано, і поверне результат. Тим часом, інші нитки можуть зробити роботу.

Якщо ви хочете отримати користь у чомусь на зразок волокон, вам потрібна підтримка в контейнері (я маю на увазі в циклі подій GUI або в сервері обробки HTTP запитів сервера), або написавши свій власний.

Наприклад, Servlet 3.0 пропонує асинхронну обробку. Пропозиції JavaFX javafx.concurrent.Task. Однак вони не мають елегантності мовних особливостей. Вони працюють через звичайні зворотні дзвінки.


2
Ось цитата статті, яка перезапускає перший абзац цієї відповіді // стартова цитата Для клієнтських додатків, таких як Windows Store, настільних ПК Windows та додатків Windows Phone, головна перевага асинхроніки - чуйність. Ці типи програм використовують async головним чином, щоб підтримувати інтерфейс користувача чутливим. Для серверних додатків головна перевага асинхронізації - масштабованість. msdn.microsoft.com/en-us/magazine/dn802603.aspx
granadaCoder

3

Ява не має нічого рідного, що дозволяє робити це як ключові слова асинхронізація / очікування, але те, що ви можете зробити, якщо дійсно хочете, це використовувати CountDownLatch . Потім ви можете імітувати асинхронізацію / очікування, передаючи це навколо (принаймні, у Java7). Це звичайна практика тестування блоків Android, де нам потрібно здійснити виклик асинхронізації (як правило, запущений, розміщений обробником), а потім чекати результату (відлік часу).

Використання цього, однак, у вашій програмі, на відміну від вашого тесту НЕ, що я рекомендую. Це було б надзвичайно соромно, оскільки CountDownLatch залежить від того, щоб ви ефективно відлічували потрібну кількість разів і в потрібних місцях.


3

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

Цій бібліотеці не потрібно розширення компілятора та реалізувати безрецептурну обробку IO в 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);
            }
            );
    }

1

На жаль, Java не має еквівалента асинхронизації / очікування. Найближчий ви можете отримати, ймовірно, з ListenableFuture від Guava та слухачів, що ланцюгується, але все ж дуже важко буде писати для випадків, пов’язаних із безліччю асинхронних дзвінків, оскільки рівень гніздування дуже швидко зросте.

Якщо у вас все гаразд із використанням іншої мови поверх JVM, на щастя, у Scala є асинхроніка / очікування, яка є прямим еквівалентом C # async / очікувати майже однакового синтаксису та семантики: 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).


1

У Java немає прямого еквівалента функції мови C # під назвою async / await, однак існує інший підхід до проблеми, яку намагається вирішити асинхрон / очікування. Це називається проект Loom , який забезпечить віртуальні потоки для одночасності з високою пропускною здатністю. Він буде доступний у деякій майбутній версії OpenJDK.

Цей підхід також вирішує " проблему кольорових функцій ", яку має асинхроніка / очікування.

Аналогічна функція також може бути знайдена в Golang ( goroutines ).


0

Якщо ви тільки після чистого коду, який імітує той же ефект, що і async / очікують у 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

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());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.