0

Hello I know there is many questions about it and some relevant answers. Like for returning response into GUI JTextArea from backend use SwingUtilities.invokeLater and for passing messages to backend using blocking queue. This I can work with.

But I would like to skip implementing queue's message parser. I would like to know if there is possible to directly call methods from another thread. Good partial answer is to call method using class implements runnable but it is only able to start a single task thread. What I'm looking for is a persistent object accepting more methods to be called from another thread and performing serialization.

So to say it more concretely:
1st thread is GUI having multiple button like "open device", "set reg A to user input", "set reg B to user input", "enable feature X", "flash FW"...

2nd is a working thread - it is already done consisting of multiple class. And having methods which needs to be called from GUI.

I need following properties
- working thread is only 1 and persistent through all GUI calls
- all GUI calls shall be serialized (another call is started only after first call is fully processed and returns)
- working thread shall be able to send some "log messages" into GUI (for example % of flashed FW) (this probably can be done easily by SwingUtilities.invokeLater)

Is there a better way to call methods than implement queue parser? If there is can you provide some link to good example? Or is the queue correct approach to this task? If the Queue is the correct approach how to best encode different parameters? Eg "flash firmware" button would need to pass "File", "set reg A to value XY" button would need to pass Byte...

Community
  • 1
  • 1
Vit Bernatik
  • 3,566
  • 2
  • 34
  • 40

2 Answers2

2

You can use Executors.newSingleThreadExecutor() to create an Executor to run tasks. Once you have the Executor instance you can send Runnable objects to it. The tasks will be queued and each task will run to completion before the next task is begun, all using the same worker thread. For instance, in your UI thread you can send a task to the executor like this:

ExecutorService executor = Executors.newSingleThreadExecutor();
...
JButton b1 = new JButton("doTask1");
b1.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) { 
        executor.execute(yourRunnable1);
    });
});

JButton b2 = new JButton("doTask2");
b2.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) { 
        executor.execute(yourRunnable2);
    });
});
mevqz
  • 653
  • 3
  • 9
  • 19
  • Don't forget to point out that Swing is not thread safe, so any updates the op might need to make in response will need to carried out from within the context of the Event Dispatching Thread and, depending the ops requirements, a SwingWorker may be a better solution – MadProgrammer Mar 09 '15 at 20:54
  • Thank you mevqz can you please look into my example if it is what you meant and thread safe? – Vit Bernatik Mar 10 '15 at 00:15
0

Based on user "mevqz" answer I made a example code. It is fully working and seems to be the good answer to my question. I'm providing this example code as elaborated answer - as it still was quite a effort for me to put it together. Also as a newbie I would like to ask if I got mevqz hint correctly and my code is truly thread safe?

Here is basically just an original Backend, where I have implemented possibility to call method log() which shall write back into GUI JTextArea in thread safe manner.

import javax.swing.*;

public class Backend {
    private int handle=0;
    private int regA=0;
    Main guiLogger;
    Backend(Main guiLog){ // interface having log() would be enough
       guiLogger = guiLog;
    }
    public void log(final String txt){
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
               guiLogger.log(txt);
            }
         });
    }

    public void open(){
        log("openning\n");
        // some code that work longer time...
        try{
            Thread.sleep(1000);
        }catch(Exception e){}
        handle++;
        log("opened: "+handle+"\n");
    }
    public void setRegA(int val){
        log("using handle:"+handle+" set reg A val: "+val+"\n");
        regA = val;
    }
}

Here is wrapper which which holds executorService and Backend reference. Here it seems not so nice as Backend is actually stored in wrong thread and always passing into Runnable.run(). Is there better way to hide Backend reference directly into ExecutorService?

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BackendRunWrapper{
    private Backend backend; // never call it from this thread
    private ExecutorService executor;

    public BackendRunWrapper(Main logger){
        backend = new Backend(logger);
        executor = Executors.newSingleThreadExecutor();
    }
    public void executorShutdown(){
        executor.shutdown();
    }
    public void open(){
        executor.execute(new Runnable(){
            public void run(){
                BackendRunWrapper.this.backend.open();
            }
        });
    }
    public void setRegA(final int val){
        executor.execute(new Runnable(){
            public void run(){
                BackendRunWrapper.this.backend.setRegA(val);
            }
        });
    }
}

Here is just a main gui with 2 buttons "Open" and "SetRegA" and JTextArea for logging. This only calls function from BackendRunWrapper. The only question here is wether executorShutdown() is called correctly?

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Main{
    BackendRunWrapper backendWrapper;
    StringBuilder sb;
    JTextArea jta;
    public void log(String txt){
        sb.append(txt);
        jta.setText(sb.toString());
    }
    public Main(){
        backendWrapper = new BackendRunWrapper(this);
        sb = new StringBuilder();

        JFrame frame = new JFrame("Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container pane = frame.getContentPane();
        pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));

        JButton b1 = new JButton("open");
        b1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                backendWrapper.open();
            }});
        pane.add(b1);

        JButton b2 = new JButton("setRegA");
        b2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                backendWrapper.setRegA(42);
            }});
        pane.add(b2);

        jta = new JTextArea(20, 80);
        pane.add(jta);

        frame.pack();
        frame.setVisible(true);

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                Main.this.backendWrapper.executorShutdown();
            }}));
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new Main();
            }});
    }
}
Vit Bernatik
  • 3,566
  • 2
  • 34
  • 40