class AbstractTextGUIThread implements TextGUIThread {

    protected final TextGUI textGUI;
    protected final Queue<Runnable> customTasks;
    protected ExceptionHandler exceptionHandler;

    /**
     * Sets up this {@link AbstractTextGUIThread} for operations on the supplies {@link TextGUI}
     * @param textGUI Text GUI this {@link TextGUIThread} implementations will be operating on
     */
    public AbstractTextGUIThread(TextGUI textGUI) {
        this.exceptionHandler = new ExceptionHandler() {
            @Override
            public boolean onIOException(IOException e) {
                e.printStackTrace();
                return true;
            }

            @Override
            public boolean onRuntimeException(RuntimeException e) {
                e.printStackTrace();
                return true;
            }
        };
        this.textGUI = textGUI;
        this.customTasks = new LinkedBlockingQueue<>();
    }

    @Override
    public void invokeLater(Runnable runnable) throws IllegalStateException {
        customTasks.add(runnable);
    }

    @Override
    public void setExceptionHandler(ExceptionHandler exceptionHandler) {
        if(exceptionHandler == null) {
            throw new IllegalArgumentException("Cannot call setExceptionHandler(null)");
        }
        this.exceptionHandler = exceptionHandler;
    }

    @Override
    public synchronized boolean processEventsAndUpdate() throws IOException {
        if(getThread() != Thread.currentThread()) {
            throw new IllegalStateException("Calling processEventAndUpdate outside of GUI thread");
        }
        try {
            textGUI.processInput();
            while (!customTasks.isEmpty()) {
                Runnable r = customTasks.poll();
                if (r != null) {
                    r.run();
                }
            }
            if (textGUI.isPendingUpdate()) {
                textGUI.updateScreen();
                return true;
            }
            return false;
        }
        catch (EOFException e) {
            // Always re-throw EOFExceptions so the UI system knows we've closed the terminal
            throw e;
        }
        catch (IOException e) {
            if (exceptionHandler != null) {
                exceptionHandler.onIOException(e);
            }
            else {
                throw e;
            }
        }
        catch (RuntimeException e) {
            if (exceptionHandler != null) {
                exceptionHandler.onRuntimeException(e);
            }
            else {
                throw e;
            }
        }
        return true;
    }

    @Override
    public void invokeAndWait(final Runnable runnable) throws IllegalStateException, InterruptedException {<FILL_FUNCTION_BODY>}
}

        Thread guiThread = getThread();
        if(guiThread == null || Thread.currentThread() == guiThread) {
            runnable.run();
        }
        else {
            final CountDownLatch countDownLatch = new CountDownLatch(1);
            invokeLater(() -> {
                try {
                    runnable.run();
                }
                finally {
                    countDownLatch.countDown();
                }
            });
            countDownLatch.await();
        }
    
<no_super_class>