tags:

views:

3283

answers:

8

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...

+6  A: 

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).

TheSoftwareJedi
That's VB6, not vb.net.
GSerg
I think you were downvoted because you said he'd be "better off using a separate Thread".
MusiGenesis
agreed, and fixed.
TheSoftwareJedi
+8  A: 

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.

GSerg
If you want to cancel quickly, you'd want to call DoEvents on each iteration.
MusiGenesis
+4  A: 

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).

MusiGenesis
hmm, same I said, but got voted down... nice...
TheSoftwareJedi
Using CreateThread in VB6 has some implications which in certain cases are too fatal to consider. It does put VB runtime in a state where it can crash when you don't expect it. So I'm not using it.Well, I am, but only to run on-the-fly-generated tiny asm procs which don't call any runtime funcs.
GSerg
I should have said "... but in VB6 it's the craziest thing you could possibly do."
MusiGenesis
Yeah, double, triple, quadruple warning on threading in VB6. You can get a demo app to work, but that is about it.
Kris Erickson
+3  A: 

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.

Bob
The only trouble here is that you also have to disable everything else except for the "Cancel" button. +1 because this answers the question directly, showing a way to allow cancellation without DoEvents, and without suggesting(horrors!) threading in VB6...
JeffK
+18  A: 

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
Joel Spolsky
+2  A: 

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

Shane
Control-PauseBreak also works great during development
rpetrich
+1  A: 

Here's an article on using the .NET BackgroundWorker component to run the task on another thread from within VB6.

MarkJ
+1  A: 

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.

MarkJ