Calling Chilkat SSH QuickCommand Freezes the Foxpro UI
Question:
My FoxPro program's UI freezes when I call Chilkat SSH QuickCommand to execute a remote command that takes a long time to complete. How can I fix it?
Answer:
This is a classic and very common issue in event-driven programming, especially with single-threaded environments like Visual FoxPro: QuickCommand()
is a synchronous (or blocking
) call.
This means your VFP application makes the call to QuickCommand()
and then completely stops and waits—it cannot do anything else—until the remote command finishes and the function returns. During this wait, it can't process Windows messages, which means it can't redraw its windows, respond to mouse clicks, or even update your WAIT WINDOW
text. This is why the GUI becomes white
and non-responsive.
The solution is to execute the command in a way that does not block the VFP UI thread. You need to periodically give control back to VFP so it can process its event loop. The key to this in VFP is a DO WHILE
loop combined with DOEVENTS
.
Here are two excellent ways to solve this using the Chilkat API. The first one (Asynchronous Task) is generally the more modern and recommended approach.
Solution: Use Chilkat's Asynchronous Task Methods
The best way to handle long-running operations in Chilkat is to use their asynchronous methods. These methods typically end with Async
. They start the task in a background thread and immediately return a Task
object. You can then poll this task object in a VFP loop to check if it's finished, without freezing your UI.
For executing a simple command, QuickCommandAsync
is perfect.
Here's how you would structure your code:
LOCAL loSsh As "Chilkat.Ssh" LOCAL loTask As "Chilkat.Task" LOCAL lcCommand As String LOCAL llSuccess As Boolean * --- Standard connection setup --- loSsh = CREATEOBJECT("Chilkat.Ssh") * --- Set your connection properties --- loSsh.Host = "your.server.com" loSsh.Port = 22 * --- Connect and Authenticate --- llSuccess = loSsh.Connect(loSsh.Host, loSsh.Port) * ... check for success ... llSuccess = loSsh.AuthenticatePw("myLogin", "myPassword") * ... check for success ... * --- Define your remote command --- * Make sure your script handles errors and logging! lcCommand = "/path/to/your/example_script.sh" * --- THIS IS THE KEY PART --- * Instead of a blocking call, use the Async version loTask = loSsh.QuickCommandAsync(lcCommand,"utf-8") IF (loSsh.LastMethodSuccess <> .T.) MESSAGEBOX(loSsh.LastErrorText) RETURN ENDIF * --- Start the background task --- * This returns immediately, allowing your VFP code to continue. llSuccess = loTask.Run() IF (llSuccess <> .T.) MESSAGEBOX(loTask.LastErrorText) RETURN ENDIF * --- Polling loop to keep the GUI alive --- WAIT WINDOW "Starting remote command... Please wait." NOWAIT NOCLEAR DO WHILE (loTask.Finished <> .T.) * Wait for a short moment. 100ms is a good balance. loTask.SleepMs(100) * DOEVENTS allows VFP to process window messages, update the GUI, * and respond to user input (though you might want to disable controls). DOEVENTS * Optional: Update the wait window * You could even get partial output here if needed, but for now, * just keeping the GUI alive is the goal. WAIT WINDOW "Remote command in progress... " + TRANSFORM(SECONDS()) + "s elapsed." NOWAIT NOCLEAR ENDDO WAIT CLEAR * --- Check the final result of the task --- * After the loop, the task is complete. Check its status. IF (loTask.StatusInt <> 7) && 7 means "Completed successfully" * It failed or was cancelled. MESSAGEBOX("The SSH command failed." + CHR(13) + loTask.ResultErrorText) ELSE * The command was sent and executed. Now get the return code from the command itself. lnExitStatus = loSsh.GetChannelExitStatus(loTask.GetResultInt()) * Retrieve the output from the background thread's QuickCommand call. lcStdOut = loTask.GetResultString() IF lnExitStatus = 0 MESSAGEBOX("Command completed successfully!") * You can display or log lcStdOut here if you want ELSE MESSAGEBOX("The remote script ran but returned an error. Exit Code: " + TRANSFORM(lnExitStatus)) * The error output (stderr) from mysqldump would be in loSsh.GetReceivedStderrB(lnChannel) * but getting stderr is easier with the second method below. ENDIF ENDIF * --- Clean up --- loSsh.Disconnect()