Реализация сопрограмм в Java - PullRequest
58 голосов
/ 17 мая 2010

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

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

  1. Вы можете реализовать «сопрограммы» в качестве потоков / пулов потоков за кулисами.
  2. Вы можете делать хитрые вещи с помощью байт-кода JVM, чтобы сделать сопрограммы возможными.
  3. В так называемой реализации JVM "Машина Да Винчи" есть примитивы, которые делают сопрограммы выполнимыми без манипулирование байт-кодом.
  4. Существуют также различные подходы к сопрограммам на основе JNI.

Я по очереди устраню недостатки каждого.

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

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

манипулирование байт-кодом JVM

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

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

Машина Да Винчи

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

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

Реализация JNI

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

Итак ...

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


Отредактировано, чтобы добавить:

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

Ответы [ 7 ]

32 голосов
/ 17 мая 2010

Я бы посмотрел на это: 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 (счетчик программ), сохраняя состояние наших текущих операций в куче, а не в стеке. Поэтому нам понадобится куча вспомогательных классов.

Например:

Допустим, в идеальном мире вы хотели написать потребителя, который выглядел бы так (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 like-операция не будет использовать стек. Итак, давайте создадим абстракцию цикла 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();
    }
}

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

8 голосов
/ 05 апреля 2017

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

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

3 голосов
/ 20 апреля 2013

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

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

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

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

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

2 голосов
/ 06 апреля 2017

Котлин использует следующий подход для сопрограмм
(от https://kotlinlang.org/docs/reference/coroutines.html):

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

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

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

0 голосов
/ 11 июня 2019

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

0 голосов
/ 24 ноября 2017

Здесь есть другой выбор для Java6 +

Реализация pythonic сопрограммы:

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());
0 голосов
/ 04 сентября 2012

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

...