web-dev-qa-db-ja.com

Javaでコルーチンを実装する

この質問は、 既存のコルーチンの実装Java )に関する私の質問に関連しています。 、それらを実装するには何が必要ですか?

その質問で言ったように、私は次のことを知っています。

  1. 「コルーチン」は、背後でスレッド/スレッドプールとして実装できます。
  2. 裏でJVMバイトコードを使用して巧妙な処理を行い、コルーチンを可能にすることができます。
  3. いわゆる「Da Vinci Machine」JVM実装には、バイトコード操作なしでコルーチンを実行可能にするプリミティブがあります。
  4. コルーチンに対するJNIベースのさまざまなアプローチも可能です。

順番にそれぞれの欠点に対処します。

スレッドベースのコルーチン

この「解決策」は病理学的です。コルーチンの要点は、スレッド化、ロック、カーネルスケジューリングなどのオーバーヘッドを avoid することです。コルーチンは軽量で高速であり、ユーザー空間でのみ実行されることになっています。厳しい制限のあるフルチルトスレッドの観点から実装すると、すべての利点がなくなります。

JVMバイトコード操作

解決するのは少し難しいですが、このソリューションはより実用的です。これは、Cのコルーチンライブラリのアセンブリ言語にジャンプするのとほぼ同じであり(これがどれだけ機能するか)、心配して正しいアーキテクチャを1つしか持たないという利点があります。

また、非準拠のスタックで同じことを行う方法を見つけられない限り、完全に準拠したJVMスタックでのみコードを実行するように拘束します(たとえば、Androidはありません)。ただし、これを行う方法を見つけた場合、システムの複雑さとテストのニーズが2倍になりました。

ダ・ヴィンチ・マシン

Da Vinci Machineは実験には適していますが、標準のJVMではないため、その機能はどこでも利用できません。実際、ほとんどの実稼働環境ではDa Vinci Machineの使用が特に禁止されていると思われます。したがって、これを使用してクールな実験を行うことはできますが、現実世界にリリースする予定のコードには使用できません。

これには、上記のJVMバイトコード操作ソリューションに似た追加の問題もあります。代替スタック(Androidなど)では機能しません。

JNIの実装

このソリューションは、これを行うポイントをJava真剣に表現します。CPUとオペレーティングシステムの各組み合わせは独立したテストを必要とし、それぞれが潜在的にイライラする微妙な障害のポイントです。完全に1つのプラットフォームに縛られることもありますが、これもJava完全に無意味です。

そう...

これらの4つの手法のいずれかを使用せずにJavaでコルーチンを実装する方法はありますか?または、代わりに最も臭いの少ない(JVM操作)を使用する必要がありますか?


追加して編集:

混乱を防ぐために、これは 私の他の に対する related の質問ですが、同じではありません。その人は、不必要に車輪を再発明することを避けるために、入札にexisting実装を探しています。これは、Javaでコルーチンを実装する方法に関する質問です。もう一方が答えられない場合。異なるスレッドで異なる質問を保持することを目的としています。

私はこれを見てみましょう: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html 、それは非常に興味深いものであり、始めるのに良い場所を提供するはずです。しかし、もちろん、Javaを使用しているので、より良い結果を得ることができます(マクロがないため、さらに悪いこともあります:))

コルーチンの私の理解から、あなたは通常producerとaconsumerコルーチンを持っています(または、少なくともこれが最も一般的なパターンです)。しかし、意味的には、非対称性を導入するため、生産者が消費者またはビザを呼び出すことは望ましくありません。しかし、スタックベースの言語が機能する方法を考えると、誰かに呼び出しをさせる必要があります。

したがって、ここに非常に単純な型階層があります。

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_doneother_stateなどのローカル変数を抽象化する必要があり、yieldのような操作ではスタックを使用しないため、whileループ自体を抽象化する必要があります。それでは、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();
}

ここでの基本的なトリックは、local変数を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();
    }
}

他の基本的な制御フロー構造でも同様のトリックを実行できます。理想的には、これらのヘルパークラスのライブラリを構築し、それらを使用してこれらの単純なインターフェイスを実装し、最終的にコルーチンのセマンティクスを提供します。ここで書いたものはすべて、一般化して大幅に拡張できると確信しています。

33
luke

JVMのKotlinコルーチン を参照することをお勧めします。ただし、別のカテゴリに分類されます。関与するバイトコード操作はなく、Androidでも動作します。ただし、コトリンでコルーチンを作成する必要があります。利点は、KotlinがJavaとの相互運用性を念頭に設計されているため、引き続きすべてのJavaライブラリを使用し、KotlinとJavaコードを自由に結合できることです。同じプロジェクト内で、同じディレクトリとパッケージに並べて配置することもできます。

この kotlinx.coroutinesのガイド はさらに多くの例を提供しますが、 コルーチンの設計 のドキュメントでは、すべての動機、ユースケース、実装の詳細が説明されています。

8
Roman Elizarov

私はちょうどこの質問に出くわし、C#と同様の方法でコルーチンまたはジェネレーターを実装できる可能性があると思います。つまり、実際にはJavaを使用しませんが、CILにはJVMと同様の制限があります。

C#の yield statement は純粋な言語機能であり、CILバイトコードの一部ではありません。 C#コンパイラは、各ジェネレーター関数に対して非表示のプライベートクラスを作成するだけです。関数でyieldステートメントを使用する場合、IEnumeratorまたはIEnumerableを返す必要があります。コンパイラは、コードをステートマシンのようなクラスに「パック」します。

C#コンパイラは、生成されたコードで「goto」を使用して、ステートマシンへの変換を容易にする場合があります。 Javaバイトコードの機能がわからず、単純な無条件ジャンプのようなものがあれば、「アセンブリレベル」では通常可能です。

すでに述べたように、この機能はコンパイラに実装する必要があります。私はJavaについての知識がほとんどなく、コンパイラであるため、コンパイラを変更/拡張できるかどうか、おそらく「プリプロセッサ」などを使用して判断することはできません。

個人的にはコルーチンが大好きです。 Unityゲーム開発者として、私はかなり頻繁にそれらを使用します。私はComputerCraftでMinecraftをたくさんプレイしているので、なぜLua(LuaJ)のコルーチンがスレッドで実装されているのか興味がありました。

3
Bunny83

コトリンは、コルーチンに次のアプローチを使用します
(from https://kotlinlang.org/docs/reference/coroutines.html )から:

コルーチンはコンパイル技術によって完全に実装され(VMまたはOS側からのサポートは不要です)、サスペンドはコード変換によって機能します。状態は呼び出しの中断に対応する状態マシンに変換されます。中断の直前に、次の状態は関連するローカル変数などとともにコンパイラ生成クラスのフィールドに格納されます。そのコルーチン、ローカル変数が復元され、ステートマシンは中断直後に状態から移行します。

中断されたコルーチンは保存され、中断状態とローカルを保持するオブジェクトとして渡されます。このようなオブジェクトのタイプはContinuationであり、ここで説明する全体的なコード変換は、従来のContinuation-passingスタイルに対応しています。したがって、サスペンド関数は、内部でContinuation型の追加パラメーターを取ります。

https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md の設計ドキュメントを確認してください

2
gooboo

Javaで使用するCoroutineクラスがあります。スレッドに基づいており、スレッドを使用すると、並列操作が可能になるという利点があります。これはマルチコアマシンでは利点になります。したがって、スレッドベースのアプローチを検討することをお勧めします。

0
Howard Lovatt

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
John Lee

また、Javaおよび Project Loom for for = JVMに拡張が行われる Quasar があります。 Youtoubeの Loom の[////]プレゼンテーションです。さらにいくつかあります。

0
OlliP