Shell Execute wait without locking UI

In my desktop app on Linux, I am running an external command via Shell.Execute. Works fine but it locks the UI and the desktop manager is interfering as it thinks the app has crashed.

Looking at the documentation, it looks like I should set the execution mode to asynchronous, and then use the DataAvailable event to capture the output from the shell command.

Does anyone have a code example of how to do this as I can’t work it out from the docs and I can’t find anything in the forum either.

You can insert a class and set its parent to Shell. You can then add the events, methods etc to that class. You can also drag Shell to a window and add events, methods etc to that.

Finally, and the hardest you can create the shell on the fly and use AddHandler to link events to it.

Thanks, Ian.

The last point you made (the hardest!) is the one i was trying to do, create the shell on the fly and add the handler. But this is the one I can’t get my head around! Any chance you have some samples?

This is what I am trying to use from the docs: AddHandler — Xojo documentation

Assuming that the DataAvailable event applies to the Shell class and operates as it does for other classes, I’d use a thread, have it do the shell.execute, and then pause itself. The event handler should do nothing other than resume the thread, and the thread would then do a readAll or whatever shell provides and process the results.

So the thread is paused until there is something to do and the UI is no longer locked up.

This is how I manage reading data from sockets connected to remote hosts.

You will also need to subclass shell and thread so you can add some properties to each, so that the thread and the shell know about each other.

Thanks, Tim. I had thought of threads but then I saw of the DataAvailable option and it seemed easier. Time to go off and learn about threading then!

The DataAvailable is just what you need to get the thread going again when there’s some results. I also add a timer so that if everything takes too long or what the shell runs gets wedged, you can have the thread bail out and let you know. The timer event also does nothing more than set a flag to indicate timout, and resumes the thread.

Just to note, I’m pretty sure a synchronous thread will still lock the UI, even if it is used within a thread, so beware of that. I’ve never bothered with the AddHandler when using Shell as I’ve almost always needed properties so it’s worth creating a subclass.

Well, obvs! That’s why the OP was talking about running it async. I’ve never used Shell, so don’t know how well a thread-based solution would work.

Yes, but putting it in a thread does not solve the problem. I was just making sure to say that.

Then what would be the best solution for this scenario? My shell process takes about 10 seconds to complete, so locks the UI. If a thread will still lock then maybe the DataAvailable with async approach is a better way?

In other languages I have done something like this (years ago):

Dim sh As New Shell
sh.CommandLine = "[my long process]"
sh.ExecutionMode = Asynchronous

Do Until  sh.Result <> ""

    [waiting action]

Loop

Here is an example. Don’t wait for actions, the method is called when the process is complete.

oShell = New Shell
oShell.ExecuteMode = Shell.ExecuteModes.Asynchronous
AddHandler oShell.Completed, AddressOf ShellCompleted

Sub ShellCompleted(oShell as Shell)
    // Do your event based stuff here. the passed shell provides a reference to the shell.
End Sub

I had to dig it out of GIT as I removed it as the new Zip and Unzip removed my need to employ a shell.

3 Likes

thank you, I will give it a try

Obviously, if you are using a thread you will have to keep the Run event busy until your Shell has completed, otherwise everything dies on you. Also, you need to use RemoveHandler before you allow the Shell to die.

1 Like

If you find yourself writing this code in an Xojo GUI application, stop, because it’s almost never, ever the right approach. :slight_smile: GUIs run on events, not polling; leave that to the framework.

1 Like

Why? You pause the thread and the rest of the app (the UI) remains responsive. I don’t know what you mean by “you will have to keep the Run event busy until your Shell has completed”? Busy doing what?

Or does a shell lock the UI even if run async and started from a thread?

It is only necessary that the Shell be asynchronous. There’s no need to start it from a thread.

But presumably you can start it from a thread. And then pause the thread so that the thread waits until it is resumed by the DataAvailable handler. And then process the results.

I don’t see what the difficulty is with this approach. You can’t do this on the main thread as there is (a) no way to pause the main thread while waiting for the shell to complete, and (b) if you just loop you lock up the UI.

Yes, you can start a Shell from a Thread.

I wouldn’t recommend the pausing approach. There might be a way to do it via a Semaphore, but I wouldn’t advise it – if something goes awry with the shell (perhaps it returns unusual results, or crashes, or never ends), your thread will never resume and the app will likely start to have issues.

Instead, have some code kick off the Shell and in the Shell’s DataAvailable or Completed event handlers (whichever is appropriate for your usage), have it start a Thread to process the results of the shell.

I’m saying that you can’t just put this in the thread Run event. As soon as the code finishes the thread stops and the shell is killed and destroyed.

Something like:

oShell = New Shell
oShell.ExecuteMode = Shell.ExecuteModes.Asynchronous
AddHandler oShell.Completed, AddressOf ShellCompleted
oShell.CommandLine = "<some long task>"

// You've got to do something here. If your shell is simple
// then Pause and Resume could well suffice.
// Otherwise you may need some sort of state engine.
		  
RemoveHandler oShell.Completed, AddressOf ShellCompleted
oShell.Close
oShell = Nil