Solved

DoEvents and Stack Errors

Posted on 1998-11-16
19
670 Views
Last Modified: 2012-08-14
I am writing an app that uses DoEvents. On a rare occasions I receive an Out of Stack Space error.

Is there way to clear the procedure stack and gracefully handle the error?

I need a really definitive response.
Thanks
0
Comment
Question by:mtriviso
  • 7
  • 6
  • 3
  • +3
19 Comments
 
LVL 12

Expert Comment

by:mark2150
ID: 1445139
There is no graceful exit from a stack error. The internal processor state cannot be saved. you have no way of knowing what was not pushed onto the stack so there is no clean out. Any attempt to perform any other operations will generally require some additional stack space (every function call requires stack, so with no stack, you can perform no functions!)

OOS is a hard DOA.
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1445140
The real answer is to remove the doevents, since they cause the stack error.
DoEvents is very dirty VB trick that can cause stack error and other crashes. Replace the doevents with timers and you will be fine.

Sample:
Old code:

sub OldSubWithDoEvents
     <Code before doevents>
     doevents
     <Code after doevents>
end sub

New code:
sub NewSubWithoutDoEvents
     <Code before doevents>
     timer1.enabled = true
end sub

sub timer1_tick
     timer1.enabled = false
     <Code after doevents>
end sub


0
 

Author Comment

by:mtriviso
ID: 1445141
Let me ask you this:

here is a snippet of my code that shows the DoEvent
Private Sub Inet1_StateChanged(Index As Integer, ByVal State As Integer)
    DoEvents
    Dim theString As String
    Dim vtData As Variant ' Data variable.
    Dim errorString As String
    Dim theMessage As String
    Dim theErrorCode As Long
    On Error GoTo errorHandler
    If mStopScanning = True Then
        Call stopEverything
        Exit Sub
    End If
    .
    .
    .

So, each time I the inet control raises a state changed event, I perform a DoEvents.

Is there any other way to keep the application responsive without using DoEvents?

Once the inet control detects that it's ready to receive data, it goes into a small loop (see below). This is where the app becomes unresponsive to user actions like moving windows around, switching to other apps, etcetera.  I just don't get it. How can I keep the app responsive without DoEvents? I do know that putting a DoEvents inside a loop is asking for disaster. And since there's no way to recover from a stack error (which is almost negligent on Microsoft's part), I think it's best to avoid DoEvents completely.
            .
            .
            .
            Select Case mState
                Case kGetPage
                    Call setPanels(kUpdate, "Receiving page", "", "")
                    Call setButton(kEnable, pictScanPage, kScanPageBright)
                    mPage = ""
                    gTimeSentInet1 = DateAdd("s", kTimeOut, Now)
                    tmrInet1.Enabled = True
                    vtData = Inet1(Index).GetChunk(kChunkSize, icString)
                    Do While LenB(vtData) > 0 And mStopScanning = False
                        ' App becomes unresponsive to gui events.
                        mPage = mPage & vtData
                        gTimeSentInet1 = DateAdd("s", kTimeOut, Now)
                        vtData = Inet1(Index).GetChunk(kChunkSize, icString)
                    Loop
                    tmrInet1.Enabled = False
                    .
                    .
                    .

All I want is for my app to play nice with everything else running. I thought DoEvents would allow me to accomplish this. Is there some other technique I can use?

Mike
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1445142
Like I said use a timer.

To replace a loop do the following

:::Old code:::
do while <condition>
     <my code>
loop
<after code>

:::new code:::
timer1.enabled = true

sub timer1_timer
    timer1.enabled = false
    if (condition)  
        <my code>
         timer.enabled = true
     else
         <after code>    
    end if
end sub
0
 

Author Comment

by:mtriviso
ID: 1445143
Hmmm.

I don't get how this technique causes the gui to remain responsive.

Also, I have lots of code that handles ansynchronous activities. This stuff is hard enough to debug without adding a new layer of complexity that seems to break up the code into an nonintuitive nightmare.

I know it's not your fault that MS designs crappy tools.

I will award you the 300 points after you explain the theory behind your technique and if you can explain to me exactly what the DoEvents command does that makes it so useless.

And while you're at it tell me what you think about all this. I was under the impression that Windows was supposed to be pre-emptively multitasked. Doesn't it seem like the onus should be on the OS to handle gui events?

Thanks for all your help.



0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1445144
What my technique does is allowing windows to intervene. Windows is controlled by messages. When you move the mouse  or press a button, a message is send. If windows cannot send you any messages since you are running in some kind of loop then your application becomes inactive.
So your application should make sure that after it has done something that you pass the control back to windows. When you exit your subroutine, windows will take back the control and send you the next message.
This is what the timer takes care of. The timer tick will be your next event to do something and it allows windows to send you messages in between.
DoEvents process also some messages, but since your subroutine is not left yet, the stack gets bigger and bigger. Press Ctrl-L in debug mode to verify.
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1445145
Replace doevents with a timer.

Compare this code.
First press button1 and type stop in the editbox. A message will appear.
First press button2 and type stop in the editbox. A message will appear.
So functionality is the same
Now press button1 and press button1 again a couple of times. Now place a breakpoint on command1_Click and look at the stack <ctrl-l>. DoEvents makes a mess of your stack.

Option Explicit


Private Sub command1_Click()
    Do While (Not testCondition)
        DoEvents
    Loop
    ConditionReached
End Sub

Private Sub Command2_Click()
    Timer1.Enabled = True
End Sub

Private Sub Form_Initialize()
    Timer1.Enabled = False
    Timer1.Interval = 1
End Sub

Private Sub Timer1_Timer()
    Timer1.Enabled = False
    If (Not testCondition) Then
        Timer1.Enabled = True
    Else
        ConditionReached
    End If
End Sub

Private Function testCondition() As Boolean
    testCondition = Text1.Text = "stop"
End Function

Private Sub ConditionReached()
    MsgBox "ok"
End Sub
0
 

Author Comment

by:mtriviso
ID: 1445146
Ok.

I'll accept your answer but have no plans to implement such a technique. It's simply too byzatine. Windows should implement their messaging properly.

I wonder, is there some other, lower level technique that I can use to trap and respond to requests from the OS without having to resort to basically re-structuring all my well-designed code to accommodate a flaw in the implementation of the DoEvents and the poor stack handling facilities in VB.

I've added another 15 points. That's all I have.
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1445147
You must be joking. You are programming in an interactive environment. That means that you are responsible for keeping the stuff alive.
DoEvents is only available in VB. Other languages don't have this "bug" called doevents.
Anyway how most other platforms solve these issues is using multi-threading. But that is more or less impossible to implement in VB.

Here is some info about multi-threading.
Here is an example from http://www.hilonet.com/vbthread/
The code
1.Assuming that we have an application that among other things can copy a file on the click of a button.

2.The copy button could have code like this:

Private Sub cmdCopyFile_Click()
Dim lpThreadID As Long
Dim hThread As Long

'spawn a new thread of execution starting with the AsyncFileCopy function
hThread = CreateThread(ByVal 0&, ByVal 0&, AddressOf AsyncFileCopy, ByVal 0&, ByVal 0&, lpThreadID)
CloseHandle hThread 'clean up after ourselves
End Sub


3.Code this for you module:

Option Explicit

Public gsOriginalFile As String
Public gsDuplicateFile As String
Declare Function CreateThread Lib "kernel32" (lpThreadAttributes As Any, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, lpParameter As Any, ByVal dwCreationFlags As Long, lpThreadID As Long) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hHandle As Long) As Long

Public Function AsyncFileCopy() As Long
'Assuming that gsOriginalFile & gsDuplicateFile
'are valid path/file names set elsewhere.

FileCopy gsOriginalFile, gsDuplicateFile
End Function

See also
http://www.microsoft.com/msj/0897/multithreading.htm

BTW: You accepted my answer, but rejected it. I don't understand.
0
Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

 
LVL 15

Expert Comment

by:ameba
ID: 1445148
You can try moving your DoEvents "lower" in your function - not using it as the first line in your Inet1_StateChanged, but later. This will allow processing more of your function and reduce calls stack.
You can increase your stack by using NT instead of Windows 95.
As a replacement for doevents, sometimes, I use Sleep API - it will allow more time to OTHER applications. I think it is better then Mirkwood's Timer solution.
Most reasons for stack fault in my experience, is because of large arrays or strings defined locally, in procedures. So, I move my big strings/arrays declarations to module level, even though they are used only locally in a procedure.
You should do the same with declaration of vtData!
0
 

Author Comment

by:mtriviso
ID: 1445149
I appreciate the help both of you have provided.

I like ameba's solution better though.

I would like to split the points between the two of you but don't know how.

Also, it simply amazes me that the DoEvents is such a piece of crap. The idea of having to declare a variable that should be local as a module level var is repugnant. I am beginning to think that VB is sizzle than steak!

So this is what I am going to do. I am going to declare the vtData as a module level var and place the DoEvents lower in the method.

Thanks for all the help.

Again, I would like to split the points between the two of you. If you can figure out how we can do that it would be great.
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1445150
You can ask Linda to split the points at the forum "Experts Exchange"
0
 
LVL 13

Expert Comment

by:Mirkwood
ID: 1445151
BTW: have you tested ameba solution. I mean. Sleep gives other applications process time. Not yours. Your application is not respondsive at all. Sleep is only usefull, if you want to pause your application.

Here is a modified sample. Pressing button 3 will hang your application.

private Declare Sub Sleep Lib "kernel32" Alias "Sleep" (ByVal dwMilliseconds As Long)

Private Sub command1_Click()
    Do While (Not testCondition)
        DoEvents
    Loop
    ConditionReached
End Sub

Private Sub command3_Click()
    Do While (Not testCondition)
        sleep 1000
    Loop
    ConditionReached
End Sub


Private Sub Command2_Click()
    Timer1.Enabled = True
End Sub

Private Sub Form_Initialize()
    Timer1.Enabled = False
    Timer1.Interval = 1
End Sub

Private Sub Timer1_Timer()
    Timer1.Enabled = False
    If (Not testCondition) Then
        Timer1.Enabled = True
    Else
        ConditionReached
    End If
End Sub

Private Function testCondition() As Boolean
    testCondition = Text1.Text = "stop"
End Function

Private Sub ConditionReached()
    MsgBox "ok"
End Sub
0
 
LVL 15

Expert Comment

by:ameba
ID: 1445152
mtriviso, thanks for the points.

And my solution is not "sleep". I didn't say it is.
Yours "timers": Do you really use timers that much?
0
 

Author Comment

by:mtriviso
ID: 1445153
no problem.

I use timers when I need them. In this case there is a setting that allows the user to set how long they want to wait before timing out. So, I use a timer to handle it.

Using a mod level var seems to have made things better. And placing the DoEvents later in the method seems better.

I have only gotten two stack errors in four months of development so hopefully the tricks you guys mentioned will make them go away completely.
0
 
LVL 15

Expert Comment

by:ameba
ID: 1445154
Hi, mtriviso
To split points, go to Customer Service, Forum "Expert Exchange" and ask a question, like:

I would like to split points between Mirkwood and ameba for question: Q.10099097
Thank you

Thank you
0
 

Author Comment

by:mtriviso
ID: 1445155
Hi. I did this and was told the points had been split. If you didn't receive your portion, you will probably have to take it to the admins. I posted my request in the forum,  "Experts Exchange" and asked Linda to do the point split.
0
 
LVL 3

Accepted Solution

by:
covington earned 310 total points
ID: 1445156
I know you've already asigned points, but you might want to look into an easier solution - replace your DoEvents calls with the API call SwitchToThread().
0
 
LVL 2

Expert Comment

by:sbmc
ID: 1445157
Hello

Im obviously a bit late for this discussion however I have a similar application running without any problems and using DoEvents. The trick is not to put it in the StateChanged event as this event is fired very frequently particulary when downloading. You should perform your Select Case routine in the command that initializes the download or whatever youre doing and leave StateChanged to set the Public variables which your main routine will act upon eg:

      kk$ = Inet1.OpenURL(thisurl)
      Do Until GotResults
        DoEvents
      Loop

The simple rule is that Doevents should never be used in  events which are fired automatically and are beyond your control.


Hope this helps


0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

Have you ever wanted to restrict the users input in a textbox to numbers, and while doing that make sure that they can't 'cheat' by pasting in non-numeric text? Of course you can do that with code you write yourself but it's tedious and error-prone …
Background What I'm presenting in this article is the result of 2 conditions in my work area: We have a SQL Server production environment but no development or test environment; andWe have an MS Access front end using tables in SQL Server but we a…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now