Is it possible to cancel out of a long running process in VB6.0 without using DoEvents?
For example:
for i = 1 to someVeryHighNumber
' Do some work here '
...
if cancel then
exit for
end if
next
Sub btnCancel_Click()
cancel = true
End Sub
I assume I need a "DoEvents" before the "if cancel then..." is there a better way? It's been awhile...
Nope, you got it right, you definitely want DoEvents in your loop.
If you put the DoEvents in your main loop and find that slows down processing too much, try calling the Windows API function GetQueueStatus (which is much faster than DoEvents) to quickly determine if it's even necessary to call DoEvents. GetQueueStatus tells you if there are any events to process.
' at the top:
Declare Function GetQueueStatus Lib "user32" (ByVal qsFlags As Long) As Long
' then call this instead of DoEvents:
Sub DoEventsIfNecessary()
If GetQueueStatus(255) <> 0 Then DoEvents
End Sub
No, you have to use DoEvents otherwise all UI, keyboard and Timer events will stay waiting in the queue.
The only thing you can do is calling DoEvents once for every 1000 iterations or such.
Is the "for" loop running in the GUI thread? If so, yes, you'll need a DoEvents. You may want to use a separate Thread, in which case a DoEvents would not be required. You can do this in VB6 (not simple).
You could start it on a separate thread, but in VB6 it's a royal pain. DoEvents should work. It's a hack, but then so is VB6 (10 year VB veteran talking here, so don't down-mod me).
Divide up the long-running task into quanta. Such tasks are often driven by a simple loop, so slice it into 10, 100, 1000, etc. iterations. Use a Timer control and each time it fires do part of the task and save its state as you go. To start, set up initial state and enable the Timer. When complete, disable the Timer and process the results.
You can "tune" this by changing how much work is done per quantum. In the Timer event handler you can check for "cancel" and stop early as required. You can make it all neater by bundling the workload and Timer into a UserControl with a Completed event.
This works well for me when I need it. It checks to see if the user has pressed the escape key to exit the loop.
Note that it has a really big drawback: it will detect if the user hit the escape key on ANY application - not just yours. But it's a great trick in development when you want to give yourself a way to interrupt a long running loop, or a way to hold down the shift key to bypass a bit of code.
Option Explicit
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Sub Command1_Click()
Do
Label1.Caption = Now()
Label1.Refresh
If WasKeyPressed(vbKeyEscape) Then Exit Do
Loop
Label1.Caption = "Exited loop successfully"
End Sub
Function WasKeyPressed(ByVal plVirtualKey As Long) As Boolean
If (GetAsyncKeyState(plVirtualKey) And &H8000) Then WasKeyPressed = True
End Function
Documentation for GetAsyncKeyState is here:
http://msdn.microsoft.com/en-us/library/ms646301(VS.85).aspx
Here is a pretty standard scheme for asynchronous background processing in VB6. (For instance it's in Dan Appleman's book and Microsoft's VB6 samples.) You create a separate ActiveX EXE to do the work: that way the work is automatically on another thread, in a separate process (which means you don't have to worry about variables being trampled).
The VB6 ActiveX EXE object should expose an event CheckQuitDoStuff(). This takes a ByRef Boolean called Quit.
The client calls StartDoStuff in the ActiveX EXE object. This routine starts a Timer on a hidden form and immediately returns. This unblocks the calling thread. The Timer interval is very short so the Timer event fires quickly.
The Timer event handler disables the Timer, and then calls back into the ActiveX object DoStuff method. This begins the lengthy processing.
Periodically the DoStuff method raises the CheckQuitDoStuff event. The client's event handler checks the special flag and sets Quit True if it's necessary to abort. Then DoStuff aborts the calculation and returns early if Quit is True.
This scheme means that the client doesn't actually need to be multi-threaded, since the calling thread doesn't block while "DoStuff" is happening. The tricky part is making sure that DoStuff raises the events at appropriate intervals - too long, and you can't quit when you want to: too short, and you are slowing down DoStuff unecessarily. Also, when DoStuff exits, it must unload the hidden form.
If DoStuff does actually manage to get all the stuff done before being aborted, you can raise a different event to tell the client that the job is finished.
EDIT it turns out the MSDN article is flawed and the technique DOESN'T WORK :(
Here's an article on using the .NET BackgroundWorker component to run the task on another thread from within VB6.
Related
I have an Internet Transfer Control on a form called "inetFTP". After I call
inetFTP.Execute , "Get " & "test.zip" & " " & "C:/test.zip"
I want to pause the code execution until the download is finished, so there wouldn't be any other code operating on the file afterwards that could encounter problems. Is there a way to do that?
Normally you'd use the control's StateChanged event and monitor for at least the icError and icResponseCompleted states.
But in real programs it is often necessary to use this along with a Timer control and an elapsed time counter and cancellation flag. You'll want to be sure you don't miss any state changes (some don't seem to fire the event if they occur in quick succession), to handle timeouts, to cancel long running operations, etc.
I suspect there are some long standing bugs in the control that have never been ironed out, which is why StateChanged isn't as reliable as one might hope. Some of this may relate to inherent quirks or race conditions in the session-oriented FTP protocol. HTTP operations seem quite a bit more deterministic.
From there you'd need to change your program flow to properly fit the model of a Windows program.
A long running async operation can be started, but then there is only so much more "worth doing" in most cases until you get a completion signal (or an abort, etc.).
So you do that Execute and then exit the event handler you are running in. Once completion is signaled you resume processing in that completion event handler.
VB6 is not QBasic, and Windows is not DOS.
You can use a Timer (VBA.DateTime.Timer), see below:
Dim PauseTime As Single, start As Single
PauseTime = 2 ' pause the execution of code for two (2) seconds:
start = Timer
Do While Timer < start + PauseTime
DoEvents
Loop
I found the answer. I should insert
Do While inetFTP.StillExecuting
DoEvents
Loop
and this loops until the Internet Transfer Control finishes it job.
What does "DoEvents" do in vb6 ?
Why do I get the error message "Out of stack space" ? What does it mean ?
DoEvents() allows other Windows messages to be processed.
The reason you get an out of stack space error is probably because DoEvents() is allowing events to occur that call your code again, which again calls DoEvents(), and so on until the stack space, which tracks the return addresses for all these calls, has run out.
In general, I do not recommend using DoEvents() due to problems like these and the fact that it violates the overall event-driven design of Windows.
A slightly different way of looking at DoEvents is that it flushes the events in the event queue. If your sub or function triggers an event, that event handler becomes a sub that is in line to run as soon as your sub/function is finished. DoEvents says to run that event handler sub now, instead of waiting till the end of your sub.
While I agree in spirit with Jonathon about not using DoEvents, I would temper his statement by saying I only recommend using it if you know exactly why, and know all of the repercussions of changing the order of the event queue this way. Most often, DoEvents is indicated when you want to update your screen in some way from within the context of a subroutine, before the subroutine is finished executing.
An example of this is when you are using the ProgressBar control. Suppose you are iterating through several thousand records, and want to provide feedback to the user as to how far along you are by updating a progress bar. You might interrupt your loop every hundred records and change the value on the progressbar control. However (unless you do something about it) you won't see the change on the screen until after the progressbar's change event handler runs, and that handler won't run until your sub is done executing. It will just get put in the event queue. The way to force the change event to run immediately, suspending your sub, is to call DoEvents. This will flush all existing events from the queue--in this case your progressbar's change event--and will update the progressbar control on the screen.
Now, "out of stack space" basically means that you've been caught in an endless loop of function calls. The most basic way to cause that is this:
Public sub MySub()
MySub
End Sub
And then call MySub from somewhere. You'll get an out of stack space error. If you look at the Call Stack, you'll see a very long line of calls to MySub.
A well-known real-world example of this would happen in older versions of VB:
Public Sub TextBoxArray_LostFocus(index as Integer)
If TextBoxArray(index) = "" Then
TextBoxArray(index).SetFocus
MsgBox "Please enter a value"
End If
End Sub
This situation assumes two members of a TextBox control array called TextBoxArray. Now, if the user starts with the first one (index 0) and moves to the second one (index 1) then index 0's LostFocus event will fire. However, VB would also internally set the focus to the index 1 box. Then the code would set the focus back to index 0, firing index 1's LostFocus event! You're caught in a loop. They fixed that in VB5 or 6 by waiting to set the focus until the LostFocus event was done executing.
I would clarify Johnathon's answer in that it pumps that VB message loop and allows the VB Runtime to process windows messages, which is the opposite of Sleep which allows for Windows to process its events (not necessary in the world of Multicore CPUs and true multitasking OS's but when VB6 was written Windows 9x was the dominant OS and a hard loop that only had DoEvents in it would spike the CPU usage to 100%). So seeing things like
While fDoneFile = False
DoEvents
Sleep 55
Wend
was a common pattern throughout the VB6 world.
As stated else where, DoEvents allows other events in your application to fire. Here's an example of how you can use DoEvents without the "Out of stack space" issue. This makes sure you don't run through the code multiple times by using a Boolean to indicate the code is running.
Sub Example()
'Create static variable to indicate the sub is running.
Static isRunning As Boolean
'Exit the sub if isRunning
If isRunning Then Exit Sub
'Indicate sub is running
isRunning = True
'Sub does stuff
DoEvents
'Ends up calling sub again
Example 'Added just to prove via testing.
'Indicate sub is no longer runningrunning
isRunning = False
End Sub
I'm working in a service whose main loop looks like this:
while (fServer.ServerState = ssStarted) and (Self.Terminated = false) do
begin
Self.ServiceThread.ProcessRequests(false);
ProcessFiles;
Sleep(3000);
end;
ProcessRequests is a lot like Application.ProcessMessages. I can't pass true to it because if I do then it blocks until a message is received from Windows, and ProcessFiles won't run, and it has to run continually. The Sleep is there to keep the CPU usage down.
This works just fine until I try to shut down the service from Windows's service management list. When I hit Stop, it sends a message and expects to get a response almost immediately, and if it's in the middle of that Sleep command, Windows will give me an error that the service didn't respond to the Stop command.
So what I need is to say "Sleep for 3000 or until you receive a message, whichever comes first." I'm sure there's an API for that, but I'm not sure what it is. Does anyone know?
This kind of stuff is hard to get right, so I usually start at the API documentation at MSDN.
The WaitForSingleObject documention specifically directs to MsgWaitForMultipleObjects for these kinds of situations:
Use caution when calling the wait
functions and code that directly or
indirectly creates windows. If a
thread creates any windows, it must
process messages. Message broadcasts
are sent to all windows in the system.
A thread that uses a wait function
with no time-out interval may cause
the system to become deadlocked. Two
examples of code that indirectly
creates windows are DDE and the
CoInitialize function. Therefore, if
you have a thread that creates
windows, use MsgWaitForMultipleObjects
or MsgWaitForMultipleObjectsEx, rather
than WaitForSingleObject.
In MsgWaitForMultipleObjects, you have a dwWakeMask parameter specifying on which queued messages to return, and a table describing the masks you can use.
Edit because of comment by Warren P:
If your main loop can be continued because of a ReadFileEx, WriteFileEx or QueueUserAPC, then you can use SleepEx.
--jeroen
MsgWaitForMultipleObjects() is the way to go, ie:
while (fServer.ServerState = ssStarted) and (not Self.Terminated) do
begin
ProcessFiles;
if MsgWaitForMultipleObjects(0, nil, FALSE, 3000, QS_ALLINPUT) = WAIT_OBJECT_0 then
Self.ServiceThread.ProcessRequests(false);
end;
If you want to call ProcessFiles() at 3 second intervals regardless of any messages arriving, then you can use a waitable timer for that, ie:
var
iDue: TLargeInteger;
hTimer: array[0..0] of THandle;
begin
iDue := -30000000; // 3 second relative interval, specified in nanoseconds
hTimer[0] := CreateWaitableTimer(nil, False, nil);
SetWaitableTimer(hTimer[0], iDue, 0, nil, nil, False);
while (fServer.ServerState = ssStarted) and (not Self.Terminated) do
begin
// using a timeout interval so the loop conditions can still be checked periodically
case MsgWaitForMultipleObjects(1, hTimer, False, 1000, QS_ALLINPUT) of
WAIT_OBJECT_0:
begin
ProcessFiles;
SetWaitableTimer(hTimer[0], iDue, 0, nil, nil, False);
end;
WAIT_OBJECT_0+1: Self.ServiceThread.ProcessRequests(false);
end;
end;
CancelWaitableTimer(hTimer[0]);
CloseHandle(hTimer[0]);
end;
Use a timer to run ProcessFiles instead of hacking it into main application loop. Then ProcessFiles will run in the interval you want and the messages will be processed correctly, not taking 100 % CPU.
I used a TTimer in a multithreaded application with strange results, so now i use Events.
while (fServer.ServerState = ssStarted) and (Self.Terminated = false) do
begin
Self.ServiceThread.ProcessRequests(false);
ProcessFiles;
if ExitEvent.WaitFor(3000) <> wrTimeout then
Exit;
end;
You create the event with
ExitEvent := TEvent.Create(nil, False, False, '');
Now the last thing is to fire the event in case of service stop. I think the Stop event of the service is the right place to put this.
ExitEvent.SetEvent;
I use this code for an cleanup thread in my DB connections pooling system, but it should work well in your case too.
You don't need to sleep for 3 full seconds to keep the CPU usage low. Even something like Sleep(500) should keep your usage pretty low (if there are no messages waiting to process it should blow through the loop pretty quick and hit the sleep again. If your loop takes a few ms to run it still means your thread is spending the vast majority of time in sleep.
That being said, your code may benefit from some refactoring. You say you don't want ProcessRequests to block waiting for a message? The only other thing in that loop is ProcessFiles. If that is dependent on the message being processed then why can't it block? And if it's not dependent on the message being processed then can it be split onto another thread? (the previous suggestion of firing ProcessFiles via a timer is an excellent suggestion on how to do this).
Use an TEvent that you signal when the thread should wake up. Then block on the tevent (using waitformultiple as Jeroen says if you have multiple events to wait on)
Is it not possible to move ProcessFiles to a seperate thread? In your MainThread you just wait for messages and when the service is being terminated you terminate the ProcessFiles thread.
In VB6, I have the following line of code in the Form_Load event:
DOSOMETHING()
MsgBox "Done"
DOSOMETHING() is a buggy function that I expect to always crash. When I run the app, it will do its thing and crash, without showing the MsgBox.
But when I write it using loops:
Dim X as Integer
For X = 0 to 1000
DOSOMETHING()
MsgBox "Done"
Next X
The application will not crash, ever. I thought that this has something to do with delays, so I also tried to add a SLEEP inside the loop, to no avail.
So my question is, Is there a special "On Error Resume Next" inside a For loop in VB6?
PS:
If anyone is curious about why I'm asking this, I am trying to reproduce an intermittent bug by calling the function multiple times. Said function is used to check for Administrator function. More detail about the function here.
Thanks!
I't might have something to do with the fact it's called from Form_Load. Perhaps some initialization later in Form_Load or in Form_Activate causes it not to crash.
Try inserting DoEvents after the call to DoSomething. This yields to the o/s, allowing events in its queue to be processed and may enable the function to complete, or fail! before returning to its calling parent.
My application needs to monitor all other running applications on the system. Is there some way I could get notified on exit of every application exe?
The methods I could find:
1) Use PSAPI functions to get the list of running exes at frequent intervals. At each poll compare with the previous list to find which application/process has exited.
Disadvantage: Requires constant polling, will take CPU time.
2) Set a global hook for WM_CLOSE message: Using this I would be able to get a notification when any application gets closed through the close button on the title bar
Disadvantage:
(-)Not all the applications are generating a WM_CLOSE message(Ex: Total Video Player Exe)
(-)If the application was closed through the "Exit" menu or button (e.g. File->Exit) , I can't trap that message
Is there any other better way that I missed? Please advise.
Get a list of PIDs using PSAPI.
Then get a handle on each process using OpenProcess().
Use WaitForMultipleObjects() to be signalled when one of the processes exits.
You could try the RegisterShellHookWindow() API and filter for HSHELL_WINDOWCREATED and HSHELL_WINDOWDESTROYED messages.
Of course, that will only get you notified about applications that have a window.
I recently ran into this problem and found a solution so wanted to share with you all. It all correct the way we should obtain handle to the process. Instead of WaitForSingleOBject though, I would recommend to use RegisterWaitForSingle object function. With this function you are giving a callback function and whenever the process exits, your callback function will be called. This is better than calling WaitForSingleObject in a thread. Calling WaitForSingleObject in your code by itself will cause your code to wait until the process exits. Here is an example of how to call it:
RegisterWaitForSingleObject(&waitHandle, processHandle, ProcessTerminatedCallback, param, INFINITE, WT_EXECUTEONLYONCE);
Where:
[out]waitHandle - new handle created for you. Please note that you cannot use this handle to call CloseHandle, but you can wait on it, if you want to.
[in] processHandle - handle to the process that you are supposed to obtain yourself
[in] ProcessTerminatedCallback - the callback function that will be called when the process exits
[in] param - LPVOID parameter that will be passed to the callback
[in] INFINITE - either wait infinitely or for a specified time, look up MSDN for more info
[in] WM_EXECUTEONLYONCE - will call the callback function only once. look up MSDN for more info
> Is there any other better way that I missed?
Yes, plenty. See on Win32 group (system notifications, without any hook)