Подтвердить что ты не робот

Реализация сопрограммы в Java

Этот вопрос связан с моим вопросом о существующих реализациях coroutine в Java. Если, как я подозреваю, выясняется, что в настоящее время на Java нет полной реализации сопрограмм, которые потребуются для их реализации?

Как я уже сказал в этом вопросе, я знаю следующее:

  • Вы можете реализовать "сопрограммы" как потоки/потоковые пулы за кулисами.
  • Вы можете делать сложные вещи с помощью байт-кода JVM за кулисами, чтобы сделать сопрограммы возможными.
  • В реализации JVM "Da Vinci Machine" реализованы примитивы, которые делают сопрограммы выполняемыми без байт-код.
  • Существуют также различные подходы JNI к сопрограммам.

В свою очередь я рассмотрю каждый недостаток.

сопрограммы на основе темы

Это "решение" является патологическим. Весь смысл сопрограмм заключается в том, чтобы избежать накладных расходов на потоки, блокировку, планирование ядра и т.д. Coroutines должны быть легкими и быстрыми и выполнять только в пользовательском пространстве. Внедрение их с точки зрения потоков с полным наклоном с жесткими ограничениями избавляет от всех преимуществ.

Обработка байт-кода JVM

Это решение более практично, хотя и немного сложно снять. Это примерно то же самое, что и переход на язык ассемблера для библиотек coroutine на C (сколько их работает) с тем преимуществом, что вам остается только одна архитектура, чтобы беспокоиться и получить право.

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

Машина Da Vinci

Машина Da Vinci классная для экспериментов, но поскольку она не является стандартной JVM, ее функции не будут доступны повсюду. Действительно, я подозреваю, что большинство производственных условий специально запрещают использование машины Da Vinci. Таким образом, я мог бы использовать это, чтобы сделать классные эксперименты, но не для какого-либо кода, который я ожидаю выпустить в реальном мире.

Это также имеет дополнительную проблему, подобную описанному выше решению для управления байт-кодом JVM: не будет работать на альтернативных стеках (например, Android).

Реализация JNI

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

Итак...

Есть ли способ реализовать сопрограммы в Java без использования одного из этих четырех методов? Или я буду вынужден использовать один из тех четырех, который пахнет наименее (манипуляция JVM)?


Отредактировано для добавления:

Просто чтобы убедиться, что путаница содержится, это связанный с этим вопрос мой другой, но не тот же. Этот пользователь ищет существующую существующую реализацию, чтобы избежать необходимости повторно изобретать колесо. Это вопрос, связанный с тем, как можно было бы реализовать сопрограммы на Java, если другой окажется неопровержимым. Цель состоит в том, чтобы задавать разные вопросы по различным темам.

4b9b3361

Ответ 1

Я бы посмотрел на это: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html, его довольно интересно и должно стать хорошим местом для начала. Но, конечно, мы используем Java, чтобы мы могли лучше (или, может быть, хуже, потому что макросов нет))

Из моего понимания с сопрограммами вы обычно используете производитель и потребитель сопрограмму (или, по крайней мере, это самый распространенный шаблон). Но семантически вы не хотите, чтобы продюсер вызывал потребителя или наоборот, потому что это вводит асимметрию. Но, учитывая то, как работают языки на основе стека, нам нужно, чтобы кто-то вызывал вызов.

Итак, вот очень простая иерархия типов:

public interface CoroutineProducer<T>
{
    public T Produce();
    public boolean isDone();
}

public interface CoroutineConsumer<T>
{
    public void Consume(T t);
}

public class CoroutineManager
{
    public static Execute<T>(CoroutineProducer<T> prod, CoroutineConsumer<T> con)
    {
        while(!prod.IsDone()) // really simple
        {
            T d = prod.Produce();
            con.Consume(d);
        }
    }
}

Теперь, конечно, сложная часть реализует интерфейсы, в частности, трудно разбить вычисление на отдельные этапы. Для этого вам, вероятно, понадобится целый набор постоянных структур управления. Основная идея заключается в том, что мы хотим имитировать нелокальную передачу управления (в конце концов, это похоже на то, что мы моделируем goto). Мы в основном хотим отказаться от использования стека и pc (program-counter), сохранив состояние наших текущих операций в куче, а не в стеке. Поэтому нам понадобится куча вспомогательных классов.

Например:

Скажем, что в идеальном мире вы хотели написать потребитель, похожий на этот (psuedocode):

boolean is_done;
int other_state;
while(!is_done)
{
    //read input
    //parse input
    //yield input to coroutine
    //update is_done and other_state;
}

нам нужно абстрагировать локальную переменную типа is_done и other_state, и нам нужно абстрагировать цикл while, потому что наша операция yield не будет использовать стек. Поэтому давайте создадим абстракцию цикла while и связанные классы:

enum WhileState {BREAK, CONTINUE, YIELD}
abstract class WhileLoop<T>
{
    private boolean is_done;
    public boolean isDone() { return is_done;}
    private T rval;
    public T getReturnValue() {return rval;} 
    protected void setReturnValue(T val)
    {
        rval = val;
    }


    public T loop()
    {
        while(true)
        {
            WhileState state = execute();
            if(state == WhileState.YIELD)
                return getReturnValue();
            else if(state == WhileState.BREAK)
                    {
                       is_done = true;
                return null;
                    }
        }
    }
    protected abstract WhileState execute();
}

Основной трюк здесь состоит в том, чтобы переместить переменные локальные в переменные class и превратить блоки областей в классы, что дает нам возможность "повторно ввести" наш цикл 'после получения нашего возвращаемого значения.

Теперь, чтобы реализовать нашего производителя

public class SampleProducer : CoroutineProducer<Object>
{
    private WhileLoop<Object> loop;//our control structures become state!!
    public SampleProducer()
    {
        loop = new WhileLoop()
        {
            private int other_state;//our local variables become state of the control structure
            protected WhileState execute() 
            {
                //this implements a single iteration of the loop
                if(is_done) return WhileState.BREAK;
                //read input
                //parse input
                Object calcluated_value = ...;
                //update is_done, figure out if we want to continue
                setReturnValue(calculated_value);
                return WhileState.YIELD;
            }
        };
    }
    public Object Produce()
    {
        Object val = loop.loop();
        return val;
    }
    public boolean isDone()
    {
        //we are done when the loop has exited
        return loop.isDone();
    }
}

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

Ответ 2

Я бы посоветовал взглянуть на сопрограммы Kotlin на JVM. Это попадает в другую категорию, хотя. Здесь нет манипуляций с байт-кодом, и это работает и на Android. Тем не менее, вам придется написать свои сопрограммы на Kotlin. Положительным моментом является то, что Kotlin предназначен для обеспечения взаимодействия с учетом Java, поэтому вы все равно можете продолжать использовать все свои библиотеки Java и свободно комбинировать код Kotlin и Java в одном проекте, даже помещая их рядом в одни и те же каталоги и пакеты.

В этом руководстве к kotlinx.coroutines приведено еще много примеров, в то время как в документе разработки сопрограмм объясняются все мотивы, варианты использования и детали реализации.

Ответ 3

Я только наткнулся на этот вопрос и просто хочу упомянуть, что я думаю, что возможно реализовать сопрограммы или генераторы аналогичным образом, как это делает С#. Тем не менее, я не использую Java, но у CIL есть такие же ограничения, как у JVM.

оператор вывода в С# является чистым языковым признаком и не является частью байтового кода CIL. Компилятор С# просто создает скрытый частный класс для каждой функции генератора. Если вы используете оператор yield в функции, он должен вернуть IEnumerator или IEnumerable. Компилятор "упаковывает" ваш код в класс statemachine.

Компилятор С# может использовать некоторые "goto's" в сгенерированном коде, чтобы упростить преобразование в statemachine. Я не знаю возможности Java-байт-кода и если есть что-то вроде простого безусловного перехода, но на уровне "сборки" это обычно возможно.

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

Лично я люблю сопрограммы. Как разработчик игр Unity, я использую их довольно часто. Поскольку я много играю Minecraft с ComputerCraft, мне было любопытно, почему сопрограммы в Lua (LuaJ) реализованы с потоками.

Ответ 4

Kotlin использует следующий подход для совлокальных процедур
(от https://kotlinlang.org/docs/reference/coroutines.html):

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

Поддержанная сопрограмма может быть сохранена и передана как объект, который сохраняет свое приостановленное состояние и локальные жители. Тип таких объектов - Continuation, а общее преобразование кода, описанное здесь, соответствует классическому стилю продолжения перехода. Следовательно, функции приостановки принимают дополнительный параметр типа Continuation под капотом.

Ознакомьтесь с проектным документом https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md

Ответ 5

У меня есть класс Coroutine, который я использую на Java. Он основан на потоках и использует потоки, имеет преимущество в разрешении параллельной работы, что на многоядерных машинах может быть преимуществом. Поэтому вы можете рассмотреть подход, основанный на потоках.

Ответ 6

Здесь еще один вариант для Java6 +

Выполнение pythonic coroutine:

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

class CorRunRAII {
    private final List<WeakReference<? extends CorRun>> resources = new ArrayList<>();

    public CorRunRAII add(CorRun resource) {
        if (resource == null) {
            return this;
        }
        resources.add(new WeakReference<>(resource));

        return this;
    }

    public CorRunRAII addAll(List<? extends CorRun> arrayList) {
        if (arrayList == null) {
            return this;
        }
        for (CorRun corRun : arrayList) {
            add(corRun);
        }

        return this;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();

        for (WeakReference<? extends CorRun> corRunWeakReference : resources) {
            CorRun corRun = corRunWeakReference.get();
            if (corRun != null) {
                corRun.stop();
            }
        }
    }
}

class CorRunYieldReturn<ReceiveType, YieldReturnType> {
    public final AtomicReference<ReceiveType> receiveValue;
    public final LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue;

    CorRunYieldReturn(AtomicReference<ReceiveType> receiveValue, LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue) {
        this.receiveValue = receiveValue;
        this.yieldReturnValue = yieldReturnValue;
    }
}

interface CorRun<ReceiveType, YieldReturnType> extends Runnable, Callable<YieldReturnType> {
    boolean start();
    void stop();
    void stop(final Throwable throwable);
    boolean isStarted();
    boolean isEnded();
    Throwable getError();

    ReceiveType getReceiveValue();
    void setResultForOuter(YieldReturnType resultForOuter);
    YieldReturnType getResultForOuter();

    YieldReturnType receive(ReceiveType value);
    ReceiveType yield();
    ReceiveType yield(YieldReturnType value);
    <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another);
    <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another, final TargetReceiveType value);
}

abstract class CorRunSync<ReceiveType, YieldReturnType> implements CorRun<ReceiveType, YieldReturnType> {

    private ReceiveType receiveValue;
    public final List<WeakReference<CorRun>> potentialChildrenCoroutineList = new ArrayList<>();

    // Outside

    private AtomicBoolean isStarted = new AtomicBoolean(false);
    private AtomicBoolean isEnded = new AtomicBoolean(false);
    private Throwable error;

    private YieldReturnType resultForOuter;

    @Override
    public boolean start() {

        boolean isStarted = this.isStarted.getAndSet(true);
        if ((! isStarted)
                && (! isEnded())) {
            receive(null);
        }

        return isStarted;
    }

    @Override
    public void stop() {
        stop(null);
    }

    @Override
    public void stop(Throwable throwable) {
        isEnded.set(true);
        if (throwable != null) {
            error = throwable;
        }

        for (WeakReference<CorRun> weakReference : potentialChildrenCoroutineList) {
            CorRun child = weakReference.get();
            if (child != null) {
                child.stop();
            }
        }
    }

    @Override
    public boolean isStarted() {
        return isStarted.get();
    }

    @Override
    public boolean isEnded() {
        return isEnded.get();
    }

    @Override
    public Throwable getError() {
        return error;
    }

    @Override
    public ReceiveType getReceiveValue() {
        return receiveValue;
    }

    @Override
    public void setResultForOuter(YieldReturnType resultForOuter) {
        this.resultForOuter = resultForOuter;
    }

    @Override
    public YieldReturnType getResultForOuter() {
        return resultForOuter;
    }

    @Override
    public synchronized YieldReturnType receive(ReceiveType value) {
        receiveValue = value;

        run();

        return getResultForOuter();
    }

    @Override
    public ReceiveType yield() {
        return yield(null);
    }

    @Override
    public ReceiveType yield(YieldReturnType value) {
        resultForOuter = value;
        return receiveValue;
    }

    @Override
    public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(CorRun<TargetReceiveType, TargetYieldReturnType> another) {
        return yieldFrom(another, null);
    }

    @Override
    public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(CorRun<TargetReceiveType, TargetYieldReturnType> another, TargetReceiveType value) {
        if (another == null || another.isEnded()) {
            throw new RuntimeException("Call null or isEnded coroutine");
        }

        potentialChildrenCoroutineList.add(new WeakReference<CorRun>(another));

        synchronized (another) {
            boolean isStarted = another.start();
            boolean isJustStarting = ! isStarted;
            if (isJustStarting && another instanceof CorRunSync) {
                return another.getResultForOuter();
            }

            return another.receive(value);
        }
    }

    @Override
    public void run() {
        try {
            this.call();
        }
        catch (Exception e) {
            e.printStackTrace();

            stop(e);
            return;
        }
    }
}

abstract class CorRunThread<ReceiveType, YieldReturnType> implements CorRun<ReceiveType, YieldReturnType> {

    private final ExecutorService childExecutorService = newExecutorService();
    private ExecutorService executingOnExecutorService;

    private static final CorRunYieldReturn DUMMY_COR_RUN_YIELD_RETURN = new CorRunYieldReturn(new AtomicReference<>(null), new LinkedBlockingDeque<AtomicReference>());

    private final CorRun<ReceiveType, YieldReturnType> self;
    public final List<WeakReference<CorRun>> potentialChildrenCoroutineList;
    private CorRunYieldReturn<ReceiveType, YieldReturnType> lastCorRunYieldReturn;

    private final LinkedBlockingDeque<CorRunYieldReturn<ReceiveType, YieldReturnType>> receiveQueue;

    // Outside

    private AtomicBoolean isStarted = new AtomicBoolean(false);
    private AtomicBoolean isEnded = new AtomicBoolean(false);
    private Future<YieldReturnType> future;
    private Throwable error;

    private final AtomicReference<YieldReturnType> resultForOuter = new AtomicReference<>();

    CorRunThread() {
        executingOnExecutorService = childExecutorService;

        receiveQueue = new LinkedBlockingDeque<>();
        potentialChildrenCoroutineList = new ArrayList<>();

        self = this;
    }

    @Override
    public void run() {
        try {
            self.call();
        }
        catch (Exception e) {
            stop(e);
            return;
        }

        stop();
    }

    @Override
    public abstract YieldReturnType call();

    @Override
    public boolean start() {
        return start(childExecutorService);
    }

    protected boolean start(ExecutorService executorService) {
        boolean isStarted = this.isStarted.getAndSet(true);
        if (!isStarted) {
            executingOnExecutorService = executorService;
            future = (Future<YieldReturnType>) executingOnExecutorService.submit((Runnable) self);
        }
        return isStarted;
    }

    @Override
    public void stop() {
        stop(null);
    }

    @Override
    public void stop(final Throwable throwable) {
        if (throwable != null) {
            error = throwable;
        }
        isEnded.set(true);

        returnYieldValue(null);
        // Do this for making sure the coroutine has checked isEnd() after getting a dummy value
        receiveQueue.offer(DUMMY_COR_RUN_YIELD_RETURN);

        for (WeakReference<CorRun> weakReference : potentialChildrenCoroutineList) {
            CorRun child = weakReference.get();
            if (child != null) {
                if (child instanceof CorRunThread) {
                    ((CorRunThread)child).tryStop(childExecutorService);
                }
            }
        }

        childExecutorService.shutdownNow();
    }

    protected void tryStop(ExecutorService executorService) {
        if (this.executingOnExecutorService == executorService) {
            stop();
        }
    }

    @Override
    public boolean isEnded() {
        return isEnded.get() || (
                future != null && (future.isCancelled() || future.isDone())
                );
    }

    @Override
    public boolean isStarted() {
        return isStarted.get();
    }

    public Future<YieldReturnType> getFuture() {
        return future;
    }

    @Override
    public Throwable getError() {
        return error;
    }

    @Override
    public void setResultForOuter(YieldReturnType resultForOuter) {
        this.resultForOuter.set(resultForOuter);
    }

    @Override
    public YieldReturnType getResultForOuter() {
        return this.resultForOuter.get();
    }

    @Override
    public YieldReturnType receive(ReceiveType value) {

        LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue = new LinkedBlockingDeque<>();

        offerReceiveValue(value, yieldReturnValue);

        try {
            AtomicReference<YieldReturnType> takeValue = yieldReturnValue.take();
            return takeValue == null ? null : takeValue.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public ReceiveType yield() {
        return yield(null);
    }

    @Override
    public ReceiveType yield(final YieldReturnType value) {
        returnYieldValue(value);

        return getReceiveValue();
    }

    @Override
    public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another) {
        return yieldFrom(another, null);
    }

    @Override
    public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another, final TargetReceiveType value) {
        if (another == null || another.isEnded()) {
            throw new RuntimeException("Call null or isEnded coroutine");
        }

        boolean isStarted = false;
        potentialChildrenCoroutineList.add(new WeakReference<CorRun>(another));

        synchronized (another) {
            if (another instanceof CorRunThread) {
                isStarted = ((CorRunThread)another).start(childExecutorService);
            }
            else {
                isStarted = another.start();
            }

            boolean isJustStarting = ! isStarted;
            if (isJustStarting && another instanceof CorRunSync) {
                return another.getResultForOuter();
            }

            TargetYieldReturnType send = another.receive(value);
            return send;
        }
    }

    @Override
    public ReceiveType getReceiveValue() {

        setLastCorRunYieldReturn(takeLastCorRunYieldReturn());

        return lastCorRunYieldReturn.receiveValue.get();
    }

    protected void returnYieldValue(final YieldReturnType value) {
        CorRunYieldReturn<ReceiveType, YieldReturnType> corRunYieldReturn = lastCorRunYieldReturn;
        if (corRunYieldReturn != null) {
            corRunYieldReturn.yieldReturnValue.offer(new AtomicReference<>(value));
        }
    }

    protected void offerReceiveValue(final ReceiveType value, LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue) {
        receiveQueue.offer(new CorRunYieldReturn(new AtomicReference<>(value), yieldReturnValue));
    }

    protected CorRunYieldReturn<ReceiveType, YieldReturnType> takeLastCorRunYieldReturn() {
        try {
            return receiveQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }

    protected void setLastCorRunYieldReturn(CorRunYieldReturn<ReceiveType,YieldReturnType> lastCorRunYieldReturn) {
        this.lastCorRunYieldReturn = lastCorRunYieldReturn;
    }

    protected ExecutorService newExecutorService() {
        return Executors.newCachedThreadPool(getThreadFactory());
    }

    protected ThreadFactory getThreadFactory() {
        return new ThreadFactory() {
            @Override
            public Thread newThread(final Runnable runnable) {
                Thread thread = new Thread(runnable);
                thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread thread, Throwable throwable) {
                        throwable.printStackTrace();
                        if (runnable instanceof CorRun) {
                            CorRun self = (CorRun) runnable;
                            self.stop(throwable);
                            thread.interrupt();
                        }
                    }
                });
                return thread;
            }
        };
    }
}

Теперь вы можете использовать pythonic сопрограммы таким образом (например, число фибоначчи)

Версия темы:

class Fib extends CorRunThread<Integer, Integer> {

    @Override
    public Integer call() {
        Integer times = getReceiveValue();
        do {
            int a = 1, b = 1;
            for (int i = 0; times != null && i < times; i++) {
                int temp = a + b;
                a = b;
                b = temp;
            }
            // A pythonic "yield", i.e., it returns `a` to the caller and waits `times` value from the next caller
            times = yield(a);
        } while (! isEnded());

        setResultForOuter(Integer.MAX_VALUE);
        return getResultForOuter();
    }
}

class MainRun extends CorRunThread<String, String> {

    @Override
    public String call() {

        // The fib coroutine would be recycled by its parent
        // (no requirement to call its start() and stop() manually)
        // Otherwise, if you want to share its instance and start/stop it manually,
        // please start it before being called by yieldFrom() and stop it in the end.
        Fib fib = new Fib();
        String result = "";
        Integer current;
        int times = 10;
        for (int i = 0; i < times; i++) {

            // A pythonic "yield from", i.e., it calls fib with `i` parameter and waits for returned value as `current`
            current = yieldFrom(fib, i);

            if (fib.getError() != null) {
                throw new RuntimeException(fib.getError());
            }

            if (current == null) {
                continue;
            }

            if (i > 0) {
                result += ",";
            }
            result += current;

        }

        setResultForOuter(result);

        return result;
    }
}

Синхронизирующая (не потоковая) версия:

class Fib extends CorRunSync<Integer, Integer> {

    @Override
    public Integer call() {
        Integer times = getReceiveValue();

        int a = 1, b = 1;
        for (int i = 0; times != null && i < times; i++) {
            int temp = a + b;
            a = b;
            b = temp;
        }
        yield(a);

        return getResultForOuter();
    }
}

class MainRun extends CorRunSync<String, String> {

    @Override
    public String call() {

        CorRun<Integer, Integer> fib = null;
        try {
            fib = new Fib();
        } catch (Exception e) {
            e.printStackTrace();
        }

        String result = "";
        Integer current;
        int times = 10;
        for (int i = 0; i < times; i++) {

            current = yieldFrom(fib, i);

            if (fib.getError() != null) {
                throw new RuntimeException(fib.getError());
            }

            if (current == null) {
                continue;
            }

            if (i > 0) {
                result += ",";
            }
            result += current;
        }

        stop();
        setResultForOuter(result);

        if (Utils.isEmpty(result)) {
            throw new RuntimeException("Error");
        }

        return result;
    }
}

Исполнение (обе версии будут работать):

// Run the entry coroutine
MainRun mainRun = new MainRun();
mainRun.start();

// Wait for mainRun ending for 5 seconds
long startTimestamp = System.currentTimeMillis();
while(!mainRun.isEnded()) {
    if (System.currentTimeMillis() - startTimestamp > TimeUnit.SECONDS.toMillis(5)) {
        throw new RuntimeException("Wait too much time");
    }
}
// The result should be "1,1,2,3,5,8,13,21,34,55"
System.out.println(mainRun.getResultForOuter());

Ответ 7

Существует также Quasar для Java и Project Loom в Oracle, где сделаны расширения для JVM для волокон и продолжений. Вот презентация Loom на Youtoube. Есть еще несколько. Легко найти с небольшим поиском.