Code executing too fast -- causes errors

T

Tony Jollans

Hi Jonathan,

Can you explain the purpose of the loop? What does doing DoEvents 20 times
do that doing it once doesn't do. Are there some events queued by the
printer action which themselves queue further events? Or is the purpose
purely a delay because the processing of the event queue fires off a
separate task and you want to wait for it to finish - and the 20 is just a
random number of iterations which seems to work? And, if that's the case
(which seems plausible in the circumstances) then shouldn't you really
explicitly wait for the printer task to post that it has finished (applying
IBM mainframe logic to this I'm afraid - I don't know enough about how
Windows works).
 
J

Jonathan West

Tony Jollans said:
Hi Jonathan,

Can you explain the purpose of the loop? What does doing DoEvents 20 times
do that doing it once doesn't do. Are there some events queued by the
printer action which themselves queue further events? Or is the purpose
purely a delay because the processing of the event queue fires off a
separate task and you want to wait for it to finish - and the 20 is just a
random number of iterations which seems to work? And, if that's the case
(which seems plausible in the circumstances) then shouldn't you really
explicitly wait for the printer task to post that it has finished
(applying
IBM mainframe logic to this I'm afraid - I don't know enough about how
Windows works).

I found that if the printer property was set and then a print done as an
entirely separate user-initiated action, all was well. Calling the
ActiveDocument.PrintOut method in the same macro immediately after setting a
printer property caused a crash requiring that Word be killed and restarted.

When I was debugging the code, I figured that there was a timing or message
queue handling problem, so I started putting in DoEvents commands until it
started behaving. It's a while ago since I wrote that code, but IIRC it
started behaving after I had put in about 8 DoEvents commands. Then, for the
published code I put in a few more on the basis that not all systems might
behave in the same way, so it would be a good idea to leave a bit of margin.
A few too many would do no harm but too few would cause crashes on machines
among users who tried out the code.

As for posting that it has finished changing, it claims that is has finished
when I set the return value of the function to CBool(iRet). But the fact is
that Windows is not behaving as it ought to, and I found that it requires
some time to catch up.

If Jezebel or anyone else can suggest another more effective approach, I'll
be very happy to learn it.


--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 
J

Jezebel

Jonathan, that do loop is a piece of nonsense. DoEvents is exactly what it
says: do any pending events. Putting it in a loop like that is just silly.
Certainly there is code that triggers outside activities (like printing)
that has to wait for the outside activity to finish before proceeding --
which is presumably what the cowboy who coded this lot was on about.

But that's not what you originally proposed: that there is Word code that
needs to be *slowed down* in order to function correctly. So again: can you
give an example?
 
J

Jonathan West

Jezebel said:
Jonathan, that do loop is a piece of nonsense. DoEvents is exactly what it
says: do any pending events. Putting it in a loop like that is just silly.
Certainly there is code that triggers outside activities (like printing)
that has to wait for the outside activity to finish before proceeding --
which is presumably what the cowboy who coded this lot was on about.

I'm that cowboy, and if you have a better way of getting round the problem
of a crash occurring if the windows message queue isn't flushed, I'll be
very happy for you to post it. I'm always keen to learn something
But that's not what you originally proposed: that there is Word code that
needs to be *slowed down* in order to function correctly. So again: can
you give an example?

That *is* Word VBA code. The fact that it also uses the Windows API is
neither here nor there.

But if you must have something that doesn't use the API, try this.

Create a userform called USerForm 1. Put on it a single label called Label1.
Then see how these two programs run

Sub ListAllFilesInMyDocuments()
Dim i As Long
On Error Resume Next
UserForm1.Show vbModeless
UserForm1.Label1.Caption = "Searching..."
With Application.FileSearch
.LookIn = Dialogs(wdDialogToolsOptionsFileLocations).Setting
.SearchSubFolders = True
.FileName = "*.*"
If .Execute Then
For i = 1 To .FoundFiles.Count
UserForm1.Label1.Caption = .FoundFiles(i)
Next i
End If
End With
Unload UserForm1
End Sub


Sub ListAllFilesInMyDocumentsWithDoEvents()
Dim i As Long
On Error Resume Next
UserForm1.Show vbModeless
UserForm1.Label1.Caption = "Searching..."

DoEvents
With Application.FileSearch
.LookIn = Dialogs(wdDialogToolsOptionsFileLocations).Setting
.SearchSubFolders = True
.FileName = "*.*"
If .Execute Then
For i = 1 To .FoundFiles.Count
UserForm1.Label1.Caption = .FoundFiles(i)
DoEvents
Next i
End If
End With
Unload UserForm1
End Sub


Also, please explain to me what you have against DoEvents. Your one post
expressing reasons said that it "is an extremely dubious practice (unless
you're also diligently switching off every possible source of interfering
input ...)", but when I have an example that *did* have a source of input
(the option to click the Stop button) you said that using DoEvents in that
context was the right thing to do.

I get the feeling that this is one of those religious arguments, like "Thou
shalt never use Goto in your programs".


--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 
T

Tony Jollans

Thank you, Jonathan.

Thinking out loud a moment ....

The time lag in 20 DoEvents is probably trivial compared to the time lag
involved in any manual action so there is no particuar reason to think that
there is any open channel specifically between the VBA code and the printer,
simply that the printer is not ready to receive any kind of communication.
It rather sounds like the request to close the printer considers itself
'successfully finished' when it's fired off some sort of printer process,
which is not unreasonable in itself but I would expect Windows to have
knowledge of what was going on and be able either to hold up or reject any
further requests to the printer till it was ready. I guess it's not a
perfect world :)
 
J

Jezebel

Also, please explain to me what you have against DoEvents. Your one post
expressing reasons said that it "is an extremely dubious practice (unless
you're also diligently switching off every possible source of interfering
input ...)", but when I have an example that *did* have a source of input
(the option to click the Stop button) you said that using DoEvents in that
context was the right thing to do.


The danger with DoEvents is that it processes ANY event in the queue, not
just the ones you're necessarily expecting. It's not that you shouldn't use
it; but that you need to use a lot of care when you do -- a lot more than
'scattering a few DoEvents around the application' implies.

To see the risk, here's a version of the example you posted a while back.

1. Create a form with two buttons and add this code --

Private mCharVal As Long

Public Event StopNow()

Private Sub UserForm_Initialize()
mCharVal = 65
End Sub

Private Sub CommandButton1_Click()

mCharVal = mCharVal + 1
With New Class1
Set .CallerForm = Me
.InsertChar mCharVal
End With

End Sub

Private Sub CommandButton2_Click()
RaiseEvent StopNow
End Sub


2. Create a class module with this code --

Public WithEvents CallerForm As UserForm1
Private mCancelled As Boolean

Public Sub InsertChar(CharVal As Long)

Do Until mCancelled
ActiveDocument.Range.InsertAfter Chr$(CharVal)
DoEvents
Loop

End Sub

Private Sub CallerForm_StopNow()
mCancelled = True
ActiveDocument.Range.InsertAfter "Stopping" & vbCr
End Sub



Now run the form. Click Button 1. Click it again. And Again. Now close the
form by clicking the X at top right, without clicking button 2. This is
obviously a trivial example, but you can see how sort of disasters that can
ensue from the indiscriminate use of DoEvents -- in this example, as with
your original version, you need to do things like disable button 1 while
mRunning is true, and broadcast the form terminate event so the spawned
classes don't run for ever.
 
T

Tony Jollans

Hi Jezebel,

Is there a way of pausing a macro to allow another process to finish (humour
me and accept that it might be required on occasion) without ceding control
to the system and letting it run whatever it sees fit? Won't sleep (for
example) have the same effect except that it will also have an explicit
amount of time before the process becomes eligible to be restarted. There is
no command that says 'selectively run other processes' or 'run other
processes except those that involve my parent' (or my children), is there?
 
J

Jezebel

OK, I've had a chance to run your two codes samples. All that's doing is
using DoEvents to refresh the form (which .Repaint will also do). But the
code actually runs perfectly well whether or not the form is repainted,
screen display notwithstanding. Do you have an example of code that does not
run correctly unless it is slowed down?
 
J

Jezebel

Of course you sometimes need to pause a macro and wait for something else to
finish. Sleep will indeed do that -- that's one of its main purposes (as
opposed to DoEvents, which is specifically for processing the event
queue) -- and, as you say, avoids the risk of unexpected events getting
processed, or conversely, eliminates the need to code for all possible
events.

I recently had to deal with some code that created, then despatched, PDFs.
The problem was knowing when the PDF is created, because Doc.Printout
returns control as soon as Acrobat has finished with the Word document; the
finalisation of the PDF happens asynchonously. The code used was something
like ...

.... printout
LoopCount = 0
Do
Sleep 550
Check if PDF exists: if yes, exit do
LoopCount = LoopCount + 1
If LoopCount > 5 then
err.raise .... something has gone wrong
End if
Loop

The 550 (or whatever it was) was arrived at by testing, to find the value
that achieved the lowest aggregate time - normally the loop iterated just
once. (An even better solution is to use the Acrobat APIs so you *know* when
the process is complete.)
 
T

Tony Jollans

Sleep will indeed do that --
-- and, as you say, avoids the risk of unexpected events getting
processed, or conversely, eliminates the need to code for all possible
events.

I didn't think I said that at all. Indeed the point of my post was to ask
whether that was the case. Let me try to be clearer.

My (limited) understanding is that:

DoEvents stops processing of the current thread in order for the system to
process the message queue (I'm not sure of terminology but I think we all
know what we mean by this). It takes as long as it takes. The current thread
is eligible for restart when the message queue has been processed. In
effect, if not in fact, the first thing that happens with DoEvents is that
the current thread restart is added to the end of the message queue.

Sleep stops processing of the current thread for an explicit amount of time.
During that time the system can do whatever it wants but I would expect it
to process the message queue. In theory the current thread could restart
before the whole of the message queue had been processed but I doubt that
Windows tries to be that clever and I suspect that the current thread
restart is added to the end of the message queue when the specified time has
elapsed.

I don't know how Windows operates and it may have multiple queues but I have
never seen anything which suggests so. Whenever any process cedes control to
the operating system it must be aware of what may happen and, if necessary,
be coded to handle it. This would seem to be as true wth Sleep as with
DoEvents.

It seems to me that, in waiting for an external process to finish, Jonathan
is running a random (hopefully sufficient) number of DoEvents and you are
suggesting a random (hopefully sufficient) number of Sleeps of a random
length. Both of you have determined what works by a process of trial and
error ('testing'). What is the difference? In both cases I think there ought
to be APIs which would be more precise but it probably isn't worth the
complexity of coding them.
 
J

Jonathan West

Jezebel said:
OK, I've had a chance to run your two codes samples. All that's doing is
using DoEvents to refresh the form (which .Repaint will also do). But the
code actually runs perfectly well whether or not the form is repainted,
screen display notwithstanding. Do you have an example of code that does
not run correctly unless it is slowed down?
I already gave you an example that crashes the system.

It doesn't sound like you are interested in a technical discussion of this.


--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 
J

Jezebel

No, you're only half understanding what's going on. DoEvents does indeed
process the message queue -- on completion (or if the queue is empty)
control returns to the line following the DoEvents. Threads are not added to
the message queue -- only events.

As you say, sleep suspends execution for a given duration, then resumes
provided other threads do not have higher priority at that moment. (The
sleep interval is thus a minimum, not an exact amount.)

The key difference between the two approaches is that DoEvents will permit
other actions within your own app to run (eg if the user clicks buttons,
closes forms, etc). Sleep doesn't do that: no other code within your app
will be initiated during the Sleep call.
 
J

Jezebel

Nothing of the kind. You began by saying that there are instances where VBA
code needs to be slowed down in order to function correctly. The link you
posted is not an example of that. That particlar instance crashes because of
the cruddy coding in that particular case.

I'd be delighted to discuss the topic if you can offer anything to
substantiate your original claim...
 
T

Tony Jollans

Thank you Jezebel,

There is clearly something fundamental I'm not understanding here, so please
bear with me.

(a) At any point in time there is a single thread running - in a single
processor environment this is by definition. This will run until it cedes
control (or, occasionally, Windows interrupts it).

(b) At the same point in time there are many other threads waiting for their
turn to run (according to priority) - again, essentially, a given.

(c) As far as I can tell from what you say, at the same point in time there
is also a queue of messages (or events) waiting to be processed. And this is
entirely separate from the threads waiting in (b) above.

So what happens when a message on the queue is processed? Might a thread get
(re)started?
If so, is it somehow queue jumping over the threads waiting in (b)
above?
If not, presumably it is added to the pool in (b) above.

If a thread doesn't get started in response to being processed from the
queue - as you are suggesting is the case with some events when a sleep is
in progress - what does happen to that message/event?
 
T

Tony Jollans

Instead of just pontificating I went and tried it.

Sleep, as far as I can see, does not send the thread to sleep, but rather
the Parent Application.

I'd like to understand the mechanics but I guess that's really a bit much
for beginners' group so I think this is probably best put to bed and I'll
dig around on my own.
 
H

Helmut Weber

Hi Tony
Sleep, as far as I can see, does not send the thread to sleep,
but rather the Parent Application.

that was what I supposed at first.

Maybe we both are just thick.
 
J

Jonathan West

Tony Jollans said:
Instead of just pontificating I went and tried it.

Sleep, as far as I can see, does not send the thread to sleep, but rather
the Parent Application.

I'd like to understand the mechanics but I guess that's really a bit much
for beginners' group so I think this is probably best put to bed and I'll
dig around on my own.

A VBA macro runs in a single thread - the main winword thread. Therefore,
sending the thread to sleep does send the parent application to sleep - they
are the same thing.


--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top