Implementing coroutines in Java

前端 未结 7 1761
你的背包
你的背包 2020-12-04 07:22

This question is related to my question on existing coroutine implementations in Java. If, as I suspect, it turns out that there is no full implementation of coroutines cur

7条回答
  •  暖寄归人
    2020-12-04 07:53

    There's an another choice is here for Java6+

    A pythonic coroutine implementation:

    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> resources = new ArrayList<>();
    
        public CorRunRAII add(CorRun resource) {
            if (resource == null) {
                return this;
            }
            resources.add(new WeakReference<>(resource));
    
            return this;
        }
    
        public CorRunRAII addAll(List arrayList) {
            if (arrayList == null) {
                return this;
            }
            for (CorRun corRun : arrayList) {
                add(corRun);
            }
    
            return this;
        }
    
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
    
            for (WeakReference corRunWeakReference : resources) {
                CorRun corRun = corRunWeakReference.get();
                if (corRun != null) {
                    corRun.stop();
                }
            }
        }
    }
    
    class CorRunYieldReturn {
        public final AtomicReference receiveValue;
        public final LinkedBlockingDeque> yieldReturnValue;
    
        CorRunYieldReturn(AtomicReference receiveValue, LinkedBlockingDeque> yieldReturnValue) {
            this.receiveValue = receiveValue;
            this.yieldReturnValue = yieldReturnValue;
        }
    }
    
    interface CorRun extends Runnable, Callable {
        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);
         TargetYieldReturnType yieldFrom(final CorRun another);
         TargetYieldReturnType yieldFrom(final CorRun another, final TargetReceiveType value);
    }
    
    abstract class CorRunSync implements CorRun {
    
        private ReceiveType receiveValue;
        public final List> 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 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  TargetYieldReturnType yieldFrom(CorRun another) {
            return yieldFrom(another, null);
        }
    
        @Override
        public  TargetYieldReturnType yieldFrom(CorRun another, TargetReceiveType value) {
            if (another == null || another.isEnded()) {
                throw new RuntimeException("Call null or isEnded coroutine");
            }
    
            potentialChildrenCoroutineList.add(new WeakReference(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 implements CorRun {
    
        private final ExecutorService childExecutorService = newExecutorService();
        private ExecutorService executingOnExecutorService;
    
        private static final CorRunYieldReturn DUMMY_COR_RUN_YIELD_RETURN = new CorRunYieldReturn(new AtomicReference<>(null), new LinkedBlockingDeque());
    
        private final CorRun self;
        public final List> potentialChildrenCoroutineList;
        private CorRunYieldReturn lastCorRunYieldReturn;
    
        private final LinkedBlockingDeque> receiveQueue;
    
        // Outside
    
        private AtomicBoolean isStarted = new AtomicBoolean(false);
        private AtomicBoolean isEnded = new AtomicBoolean(false);
        private Future future;
        private Throwable error;
    
        private final AtomicReference 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) 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 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 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> yieldReturnValue = new LinkedBlockingDeque<>();
    
            offerReceiveValue(value, yieldReturnValue);
    
            try {
                AtomicReference 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  TargetYieldReturnType yieldFrom(final CorRun another) {
            return yieldFrom(another, null);
        }
    
        @Override
        public  TargetYieldReturnType yieldFrom(final CorRun another, final TargetReceiveType value) {
            if (another == null || another.isEnded()) {
                throw new RuntimeException("Call null or isEnded coroutine");
            }
    
            boolean isStarted = false;
            potentialChildrenCoroutineList.add(new WeakReference(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 corRunYieldReturn = lastCorRunYieldReturn;
            if (corRunYieldReturn != null) {
                corRunYieldReturn.yieldReturnValue.offer(new AtomicReference<>(value));
            }
        }
    
        protected void offerReceiveValue(final ReceiveType value, LinkedBlockingDeque> yieldReturnValue) {
            receiveQueue.offer(new CorRunYieldReturn(new AtomicReference<>(value), yieldReturnValue));
        }
    
        protected CorRunYieldReturn takeLastCorRunYieldReturn() {
            try {
                return receiveQueue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            return null;
        }
    
        protected void setLastCorRunYieldReturn(CorRunYieldReturn 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;
                }
            };
        }
    }
    

    Now you can use pythonic coroutines in this way (e.g. fibonacci numbers)

    Thread Version:

    class Fib extends CorRunThread {
    
        @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 {
    
        @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;
        }
    }
    

    Sync(non-thread) version:

    class Fib extends CorRunSync {
    
        @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 {
    
        @Override
        public String call() {
    
            CorRun 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;
        }
    }
    

    Execution(Both versions will work):

    // 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());
    

提交回复
热议问题