mtriviso
asked on
DoEvents and Stack Errors
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
Is there way to clear the procedure stack and gracefully handle the error?
I need a really definitive response.
Thanks
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
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
ASKER
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(kChu nkSize, 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(kChu nkSize, 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
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(kChu
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(kChu
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
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
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
ASKER
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.
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.
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.
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.
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
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
ASKER
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.
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.
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.
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.
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!
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!
ASKER
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.
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.
You can ask Linda to split the points at the forum "Experts Exchange"
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
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
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?
And my solution is not "sleep". I didn't say it is.
Yours "timers": Do you really use timers that much?
ASKER
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.
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.
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
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
ASKER
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.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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
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
OOS is a hard DOA.