0
votes

Problem: I need 3 threads. Thread1 reads an int-value and starts an action (that involves a UI) if this value changes. Thread2 is the UI. Thread3 is the action that should be performed when Thread1 notices that the int changed. Now, when I start thread2 and then do

    display.async(new Thread1())

it won't show the UI since the open()-method is called later. When I do open() first and then display.async() it'll throw an SWTException immediately:

    Exception in thread "Thread-0" org.eclipse.swt.SWTException: Failed to execute runnable (org.eclipse.swt.SWTException: Invalid thread access)
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.widgets.Synchronizer.runAsyncMessages(Unknown Source)
        at org.eclipse.swt.widgets.Display.runAsyncMessages(Unknown Source)
        at org.eclipse.swt.widgets.Display.readAndDispatch(Unknown Source)
        at sokobangui.SokobanGUIManager.run(SokobanGUIManager.java:57)
        at java.lang.Thread.run(Unknown Source)
    Caused by: org.eclipse.swt.SWTException: Invalid thread access
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.widgets.Display.checkDisplay(Unknown Source)
        at org.eclipse.swt.widgets.Display.create(Unknown Source)
        at org.eclipse.swt.graphics.Device.(Unknown Source)
        at org.eclipse.swt.widgets.Display.(Unknown Source)
        at org.eclipse.swt.widgets.Display.(Unknown Source)
        at sokobangui.SokobanGUIManager.run(SokobanGUIManager.java:35)
        at java.lang.Thread.run(Unknown Source)
        at org.eclipse.swt.widgets.RunnableLock.run(Unknown Source)
        ... 5 more

When I start thread1 and thread2 seperately without trying to sync them it works fine until I try to initialize thread3 by using thread2. Then I get this one:

    Exception in thread "Thread-1" org.eclipse.swt.SWTException: Invalid thread access
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.SWT.error(Unknown Source)
        at org.eclipse.swt.widgets.Widget.error(Unknown Source)
        at org.eclipse.swt.widgets.Widget.checkWidget(Unknown Source)
        at org.eclipse.swt.widgets.Control.getShell(Unknown Source)
        at sokobangui.GUICoordinator.switchToRoboControl(GUICoordinator.java:120)
        at sokobangui.GUIListener.run(GUIListener.java:15)
        at java.lang.Thread.run(Unknown Source)

So if I was asked what happened here I'd say there's some problem with sychronizing the threads since as far as I know the display-thread needs to 'know' which threads will modify the surface. However I have no idea how to solve that problem...

Here're some code snippets: (thread1 = GUIListener; thread2 = SokobanGUIManager; thread3 = SwitchToRoboControl)



    public class StartSokobanSolver {
        public static void main(String[] args) {
            GUIStatus.status = GUIStatus.MAIN;

            Thread manager = new Thread(new SokobanGUIManager());
            manager.start();

            //Thread listener = new Thread(new GUIListener());
            //listener.start();
        }
    }

    public class SokobanGUIManager implements Runnable {
        public void run() {
            this.display = new Display();
            this.display.asyncExec(new Thread(new GUIListener()));

            this.shell = new Shell(this.display);

            goToMainMenu();

            this.shell.open();

            while (!this.shell.isDisposed()) {
                if (!display.readAndDispatch()) {
                display.sleep();
                }
            }
            GUIStatus.status = GUIStatus.END;
        }
    }

    public class GUIListener implements Runnable {
        private static GUICoordinator connector;

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                //try{
                    while(GUIStatus.status != GUIStatus.END) {
                        System.out.println("GUI-Status: " + GUIStatus.status);
                        if(GUIStatus.status == GUIStatus.INGAME_ROBOT) {
                            System.out.println("InGameRobot Start");
                            connector.switchToRoboControl();
                            System.out.println("InGameRobot End");
                            GUIStatus.status = GUIStatus.INGAME_SOLVED;
                            connector.isSolved();
                        } else {
                            System.out.println("Else");
                            try{ Thread.sleep(5000); } catch(Exception e) {}
                        }
                        if(GUIStatus.status == GUIStatus.END) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    /*} catch(Exception e) {
                    System.out.println("Ende des Threads : " + e.toString());
                    Thread.currentThread().interrupt();
                    Not in the code atm to get the full exception message!
                }*/
            }
        }

        protected static void setCoordinator(GUICoordinator c) {
            connector = c;
        }
    }

    public class GUICoordinator {
        ...
        protected void switchToRoboControl() {
            if(this.roboControl != null) {
                Solution solution = new Solution("1u:1l:1d"); 
                Thread roco = new Thread(new SwitchToRoboControl(this.map, this,  this.mapArea.getShell(), solution));
                Display.getDefault().asyncExec(roco);
                roco.start();
                System.out.println("Thread started");
            } else {
                System.out.println("Epic fail");
            }
        }
    }

    public class SwitchToRoboControl implements Runnable {
        ...

        public SwitchToRoboControl(Map map, GUICoordinator gui, Shell shell, Solution solution) {
            this.map = map;
            this.gui = gui;
            this.shell = shell;
            this.solution = solution;
        }

        @Override
        public void run() {
            ...action to be performed
        }
    }

1
So, Thread3 is the one executing action performed when Thread1 discover a state change, right? Maybe some code snippets would explain your use case better. Do you mind sharing them?Pawel Pogorzelski
I added some snippets, I hope that helps understanding the issue. But you're right, thread3 (=SwitchToRoboControl) is supposed to execute some action that is supposed to be visible on the UI (moving icons basically), if, and only if, thread1 (=GUIListener) notices a state change.Saftkeks
Does GUIListener perform any UI actions? I mean actions executed synchronously.Pawel Pogorzelski
It doesn't. It only calls connector.switchToRoboControl() which goes into the GUICoordinator.switchToRoboControl() and THAT starts the 3rd thread that performs UI actions (or is supposed to). The GUIListener really just reads the value and calls that method.Saftkeks
What changes the value of GUIStatus.status to GUIStatus.INGAME_ROBOT?Pawel Pogorzelski

1 Answers

0
votes

Don't start GUIListener at all. Use menu listener for "Solve Riddle by Robot" which will schedule game solving and then perform some UI actions. This can be achieved in two ways:

1) Solving game directly in "Solve Riddle by Robot" listener

2) Starting a thread which computes the solution and then uses "Display.asyncExcec" (or syncExec) to present the solution in the UI

See here how to attach a listener to a MenuItem.

Hope that helps.