0

I have created a Desktop Application using Java Swing. It takes some input from user, creates a config file and a batch file to run a python scripts. Many concerns are:
- I want the GUI to be in active mode when the batch file execution in progress
- There is a button like ShowLog in the app to check the console output at during execution. That should work on clicking
- I have a "Task in Progress" kind of message in GUI which should be replaced by "Task is Completed" when batch file execution is done
- A "Stop" button also is there to stop the batch file execution forcefully. That should work fine as well
(Note: The batch file execution will take hours to complete)

Can anybody come up with some ideas how I can achieve all these?

Adrien Lemaire
  • 1,294
  • 2
  • 13
  • 23

2 Answers2

1

As you seem to be aware, Swing is a single threaded framework, which means that anything that is run within the context of the Event Dispatching Thread will prevent it from updating the screen or responding to user input.

The basic solution would be to use a Thread to run the batch process in, but this raises issues with synchornisation of updates to the UI, as you should never modify or interact with the UI from outside the context of the EDT.

A better solution would be to use a SwingWorker, which provides you with the ability to run long running tasks in the background, but provides you with the ability to publish updates to and process updates within the context of the EDT, it also provides you with a done method which is called after the doInBackground method exits and is called within the context of the EDT.

Finally, it provides you with a cancel option - This, however is where the problem occurs. Presumably you will be reading the input from the process in a secondary thread and will be waiting for the process to exit within the same thread (SwingWorker) you started it. SwingWorker relies on the interrupt funcitonality of Thread which may not trigger the waitFor method to return.

Having now gone a read the Process documentation, waitFor does throw an InterruptedException

if the current thread is interrupted by another thread while it is waiting, then the wait is ended and an InterruptedException is thrown.

This would suggest that when done is called, you would need to call isCancelled to check if the worker was cancelled or not. If it was you would need to call destroy on the Process and shut down any secondary Threads you might have running.

You could use an additional SwingWorker to read the input from the process and utilise it's publish/process functionality to update the logs.

This would mean, you would start a SwingWorker to execute your external process. This would presumably be done in response to some event, like a button push.

When this worker's doInBackground method is called, it would execute the external process and call Process#waitFor. This would stop the doInBackground method from returning until the process has exited.

Before you call Process#waitFor, you could create another SwingWorker and pass the Process's OutputStream to it. This would allow this worker to process the output from the process independently. You would then be able to use this to send output of the process back to the EDT via the SwingWorker's publish/process functionality which could be added to something like a JTextArea.

This would save you a lot of hassle with dealing with SwingUtilities.invokeLater.

Do you need the second work? That depends on what you want to the workers to do. I tend to process all the output of external process in separate threads and allow who ever created the process to use waitFor, it isolates the responsibility a little more and prevents the IO from getting locked up an never reaching waitFor, but that's just me.

Take a look at Concurrency in Swing for more details

MadProgrammer
  • 323,026
  • 21
  • 204
  • 329
  • I think that this is idiocity. The new process will have its own "SwingWrokers". You do not need a thread to execute a parallel process. – Val Feb 19 '14 at 06:17
  • @Val How do you mean? I typically use a separate thread to read the processes `OutputStream` while allowing the `Thread` that started to `waitFor` – MadProgrammer Feb 19 '14 at 06:23
  • @Val Can you please explain your view more elaborately? – Abhishek Mukherjee Feb 19 '14 at 06:28
  • @MadProgrammer Thanks for your new idea. But what I am thinking, I have an existing GUI already designed in NetBeans. Can I integrate SwingWorkers to the existing setup, or need to start from scratch? – Abhishek Mukherjee Feb 19 '14 at 06:30
  • Nope, you should be able to integrate it quite simply. Basically, you would only need to take the portion of code that executes and monitors the external process and wrap it within the `SwingWorker`. Just make sure you maintain a reference to it so you can call it's `cancel` method – MadProgrammer Feb 19 '14 at 06:32
  • @Val I should also point at that if you wish to monitor the state of an external process from within Swing, you need to do it from outside of the Event Dispatching Thread, otherwise it will "freeze" the program. If you didn't care about the return value or the output of the external process, then no, you wouldn't need a thread, but that wasn't the question – MadProgrammer Feb 19 '14 at 06:44
  • Thank you so much @MadProgrammer. I am now studying the link you have provided. Hope I'll come up with all the solutions with minimum effort. – Abhishek Mukherjee Feb 19 '14 at 06:54
  • @MadProgrammer The purpose of the first SwingWorker is not clear at all in your answer. Do you need a SwingWorker to execute it? Do you need a SwingWorker to create/execute a SwingWorker? You know, the GUI will block your Event Dispatching Thread if you do not do that. – Val Feb 19 '14 at 06:56
  • @Val I did suggest using two `SwingWorkers`. The first to execute the external process and call `Process#waitFor`, which will prevent the `doInBackground` method from exiting until the `Process` exists. This would then call `done`, which would allow the OP to update the state of the UI ("Task Completed"). The second `SwingWorker` would be used to process the `Process`'s `OutputStream`, using the `publish` method to push output to the `process` method which could update the log information (which I imagined would go straight to something like a `JTextArea`) – MadProgrammer Feb 19 '14 at 07:01
  • @Val Would you need two, yes and no. Depends what you hope to accomplish. And sorry, the first `SwingWorker` would start the second. It is only suggested this way as then you don't need to mess with `SwingUtilities.invokeLater`, which gets really messy real quick. – MadProgrammer Feb 19 '14 at 07:03
  • Ok, the point seems to be that ProcessBuilder API seems to be as stupid as the blocking IO, which demands users to create a thread just to do nothing (wating = doing nothing). – Val Feb 19 '14 at 07:06
0

You can run bat file in java with Runtime

Runtime.getRuntime().exec("cmd /c start your_batch_file.bat");
Dark Knight
  • 7,757
  • 4
  • 33
  • 55