Как стать автором
Обновить

Комментарии 25

Академически — круто:
показано, что можно прервать программу на середине, а потом восстановить выполнение, подсунув тот же стек.

Практически — пока не видно удобства:
вместо обещанного
data = readSocket();

мы получаем код, который визуально не проще работы с колбеками:
Coro coro = Coro.initSuspended((@Async({@Await(value = "yield")}) ICoroRunnable) () -> {
Coro.get().yield();
});


Спасибо автору за расширение моего кругозора, но видимо следующим шагом надо писать свой новый язык JVM, где можно использовать свои собственные ключевые слова async и await.
Если писать сервер, то этот ужасный код будет только в самом верху стека обработки запроса, далее писать код можно обычными методами, только следя за аннотациями на точках восстановления. А все асинхронные операции достаточно один раз обернуть в сопрограммы и положить в утилитный класс, и больше его не трогать, а пользоваться ими как синхронными вызовами. См пример
следующим шагом надо писать свой новый язык JVM, где можно использовать свои собственные ключевые слова async и await
Для желающих они реализованы в стандартной scala-library, на базе Future (в java8 запилили аналогичный, хотя и более простой, CompletableFuture).
Подскажите, а в чем корневое отличие данного подхода от Future и CompletableFuture?
В том, что не нужно использовать promises и коллбеки, а можно просто писать код, как если бы он был синхронным.
По-моему слишком много магии.
Akka и rx frameworks выглядят гораздо приятнее и надежнее.
Конечно, надёжнее, ведь там не инструментируется байт-код :) но, я надеюсь, что и этот механизм будет доведён до должного уровня доверия
Там нужно писать коллбеки, именно этого и хочется избежать.
По-моему проще написать обертку для vert.x, чтобы избежать callback. Разве нет?
Из документации vert.x 3: RxJava style APIs if you don't like callbacks.

Думаю это как раз то что нужно чтобы избежать коллбеки.
Я если правильно понимаю так:
Action1<HttpServer> onNext = httpServer -> {};
Action1<Throwable> onError = httpServer -> {};
Action0 onComplete = () -> {};

Handler<AsyncResult<HttpServer>> handler1 = RxHelper.toFuture(onNext);
Handler<AsyncResult<HttpServer>> handler2 = RxHelper.toFuture(onNext, onError);
Handler<AsyncResult<HttpServer>> handler3 = RxHelper.toFuture(onNext, onError, onComplete);
Кажется, это те же коллбеки, записанные иначе (заранее сохранённые в отдельную переменную и потом использованные). Но дело даже не в том, что коллбеки плохи сами по себе. Проблема в неудобствах, которые возникают, когда нам нужно результат одной операции передать в следующую. Плюс, надо как-то уметь обрабатывать ошибки. А если писать синхронный код, таких проблем не возникает — всё просто и понятно. В этом суть подхода.
Как же данная проблема решается в том же vert.x?
Предполагаю, что написанием этого неудобного кода :) не знаю, как решить эту задачу удобно и без сопрограмм.
Немного изучил тему. И опять же нашел вариант у то во же vert.x. Для задач, где необходимо выполнить методы последовательно и для проблемы с обработкой ошибок в частности предлагается использовать Vertx-Sync. Они так и пишут:
The non blocking nature of Vert.x leads to asynchronous APIs. Asynchronous APIs can take various forms including callback style, promises or Rx-style. Vert.x uses callback style in most places (although it also supports Rx).

In some cases, programming using asynchronous APIs can be more challenging than using a direct synchronous style, in particular if you have several operations that you want to do in sequence. Also error propagation is often more complex when using asynchronous APIs.

Vertx-sync allows you to work with asynchronous APIs, but using a direct synchronous style that you’re already familiar with.


Не знаю насколько это удобно, тут важно понять окупаются ли все эти неудобства с той мощностью что дает vert.x из коробки.
Посмотрел на Vertx-Sync, там написано, что оно работает на базе Quasar, который, как я понимаю, использует аналогичные jcoro механизмы. Ну то есть там тоже есть восстанавливаемый контекст выполнения и инструментирование байт-кода. Так что даже не знаю, что проще — взять одну jcoro или пробовать vertx-sync, который зависит от quasar и vertx, которые сами по себе не маленькие :)
Вот, кстати, еще один живой проект github.com/puniverse/quasar, имеющий цели схожие с вашими. Без поддержки на уровне языка, все это все же выглядит не слишком удобно, как мне кажется.
Согласен, поддержки на уровне языка очень не хватает…
В этом аспекте С++ выглядит интереснее: поддержки на уровне языка — нет, а, тем не менее, использовать корутины сильно проще.
В своём проекте TeaVM я тоже сделал сопрограммы, вдохновившись опытом javaflow. Более того, я даже сделал эмуляцию тредов на них (ну а как ещё сделать треды в JS?), вот работающий пример.

В отличие от javaflow код сильно не разбухает, т.к. я произвожу статический анализ всего кода, и могу определить, какие методы никогда не будут вызывать, прямо или косвенно, сопрограммы, и таким образом не транформировать их. Кроме того, я не добавляю код по сохранению состояния метода после каждого invoke'а, а просто вставляю goto на общий код (таким образом получается некоторый оверхед по размеру сохранённого состояния). Так же поддерживаются лямбды, но не поддерживается reflection, потому что сам TeaVM не поддерживает reflection.

Большой проблемой было то, что вставляя развесистый tableswitch в начало метода, мы получаем чаще всего неприводимый граф потока, который нельзя перевести в структурные операторы JavaScript. Эту проблему я решил частично разрезанием графа, частично node splitting'ом.

Вот пример того, как создаются сопрограммы:

    @Async static native String get(String url) throws IOException;

    static void get(final String url, final AsyncCallback<String> callback) {
        XMLHttpRequest xhr = XMLHttpRequest.create();
        xhr.overrideMimeType("text/plain; charset=x-user-defined");
        xhr.onComplete(() -> {
            if (xhr.getStatus() != 200) {
                callback.error(new IOException("Error loading remote resource " + url + ". Status: " +
                        xhr.getStatus() + " " + xhr.getStatusText()));
                return;
            }
            callback.complete(xhr.getResponseText());
        });
        xhr.open("get", url);
        xhr.send();
    }


Ну а пример использования такого метода:

System.out.println("Fetching data...");
System.out.println(get("http://localhost:8080"));
System.out.println("Complete");


Соответственно, Complete будет напечатан после содержимого.

К сожалению, всё очень сильно завязано на инфраструктуру TeaVM, и для просто Java работать не будет.
Круто! Я не решился на статический анализ, решил для начала размечать аннотациями вручную. Статический анализ действительно хорошо работает в этом месте?
К сожалению, статический анализ не будет работать в JVM. В TeaVM я сознательно с самого начало убил reflection ради возможности строить method call graph, и исходя из моих целей это было оправдано. Когда потом уже понадобилось делать сопрограммы, method call graph оказался очень кстати.
Периодически наблюдаю за вашей TeaVM — с того момента, как Вы статью на хабре написали. Сам писал на GWT, т.ч. интерес был не праздный. Но статей уже давно небыло. Не планируете написать ещё стаью о текущем состоянии дел с TeaVM? На github находил статьи на английском, но может тут напишете?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации