Hi,
I am curious what the general feeling is about DoEvents, and using it at multiple places in code, especially in "long-winded" For/Next loops?
Printable View
Hi,
I am curious what the general feeling is about DoEvents, and using it at multiple places in code, especially in "long-winded" For/Next loops?
You don't need to place em in every for next loop. DoEvents does tend to slow the program down a notch if used in multiple places. The only time its really needed in a For...Next loop is when you loading 100s-1000s of things and should be placed at the end of your loop. If in a Do Loop however, you need it at the end of the loop as well.
inside a loop is fine. Putting it anywhere in the loop is generally fine. Typically, it's done at the end of the loop. What I wouldn't recommend is having the DoEvents execute EVERY iteration of the loop. I'd add a counter, and then check it's value and execute the DoEvents, maybe once every 10 or so loops...
-tg
DoEvents allows re-entrance into any routine in your project. You may need to protect against this
In my opinion, you can use DoEvents() in the loop (normally is While loop), and of course you can use anywhere in your project (as LaVolpe said). Have fun!
Opinion about Doevents ? OMFG its unbelievably effin dangerous. I get nightmares about it......I wrote one application in the past where I could not avoid using it and goddamn!!! ENDLESS HEADACHES. Was nothing short of a miracle I got that app to work at all.
IF YOU CAN AVOID IT DO NOT USE IT. I SAY TO YOU DONT USE IT IF YOU CAN AVOID IT
Quite a few years ago I was doing some experimenting with different methods other than DoEvents and found DoEvents to be the slowest. TranslateMessage and DispatchMessage, although not shown in the DoEvents_Fast() sub even though its another alternative, is commonly used in C++. Note that this code was from many many years ago.
vb Code:
Option Explicit Public Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As MSG, ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long Public Declare Function GetInputState Lib "user32" () As Long Public Declare Function GetQueueStatus Lib "user32" (ByVal qsFlags As Long) As Long Public Declare Function GetMessage Lib "user32" Alias "GetMessageA" (lpMsg As MSG, ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long) As Long Public Declare Function TranslateMessage Lib "user32" (lpMsg As MSG) As Long Public Declare Function DispatchMessage Lib "user32" Alias "DispatchMessageA" (lpMsg As MSG) As Long Public Const WM_SYSCOMMAND As Long = &H112 Public Const WM_CLOSE As Long = &H10 Public Const WM_DESTROY As Long = &H2 Public Const PM_NOREMOVE As Long = &H0 Public Const QS_HOTKEY = &H80 Public Const QS_KEY = &H1 Public Const QS_MOUSEBUTTON = &H4 Public Const QS_MOUSEMOVE = &H2 Public Const QS_PAINT = &H20 Public Const QS_POSTMESSAGE = &H8 Public Const QS_SENDMESSAGE = &H40 Public Const QS_TIMER = &H10 Public Const QS_ALLINPUT = (QS_SENDMESSAGE Or QS_PAINT Or QS_TIMER Or QS_POSTMESSAGE Or QS_MOUSEBUTTON Or QS_MOUSEMOVE Or QS_HOTKEY Or QS_KEY) Public Const QS_MOUSE = (QS_MOUSEMOVE Or QS_MOUSEBUTTON) Public Const QS_INPUT = (QS_MOUSE Or QS_KEY) Public Const QS_ALLEVENTS = (QS_INPUT Or QS_POSTMESSAGE Or QS_TIMER Or QS_PAINT Or QS_HOTKEY) Public Sub DoEvents_Fast() 'This does events only when absolutely necessary and still prevents 'your program from locking up. The result is a Do loop that is 'multiple times faster than an ordinary Do/DoEvents/Loop, which is needed for 'realtime loops. I've experimented with multiple methods I've found on Planet 'Source Code, and here are my results: 'Note - This all has been done on my AMD Athlon 1.2 Ghz Processor. Results may vary. '-------------------------------------------------------------------------------------- 'Highest durations per second '-------------------- 'VB - 192136 'Exe - 296140 'Slow, slugish, and ugly. 'DoEvents '------------------------------------------------------------------------------------- 'Highest durations per second '-------------------- 'VB - 688950 'Exe - 735468 'If PeekMessage(Message, 0, 0, 0, PM_NOREMOVE) Then ' DoEvents 'End If '-------------------------------------------------------------------------------------- 'Highest durations per second '-------------------- 'VB - 965230 'Exe - 1113434 'Problem with this is that it's only active when an event 'has occured. With this I just simply held a key down. 'If GetInputState() Then ' DoEvents 'End If '------------------------------------------------------------------------------------- 'Highest durations per second '-------------------- 'VB - 947204 'Exe - 1101420 'This is the fastest and most reliable method so far. If GetQueueStatus(QS_HOTKEY Or QS_KEY Or QS_MOUSEBUTTON Or QS_PAINT) Then DoEvents End If End Sub
Heheh. Niya, I feel your pain. But I think it is the only solution in many instances. Odd thing was, until just recently, I never really had any issues with it. And I've been using vb since 1998! But this week it took me about a day and a half to figure out that DoEvents was my enemy. Er...rather, my usage of DoEvents was my enemy!
What I had was a situation where procedures were calling procedures were calling procedures, and in those those procedures were a bunch of loops that went round and round, some processing maybe a 1000 cycles. So if I didn't put a DoEvents inside the really long-winded loops, I had no way to get control back until they were finished. To make a long story short, whenever I would click a Pause/Continue toggle button to stop them in they're tracks, it took all control to an idle procedure which was an infinite Goto loop with another DoEvents inside it. If I just re-clicked Pause/Continue, everything would be fine. But if I clicked the Proceed button (which essentially starts everything from scratch, THAT's when the world came apart at the seams. The program would function fine, seem to finish, and then go haywire, going to seemingly random spots in the code. But in sorting it all out, it wasn't random at all. I realized that even though I essentially restarted, I still had a bunch of calls from the old aborted attempt, just garbage now sitting in the stack. It was simply processing the garbage calls. And those caused really odd behavior in the loops...Like ignoring the For statement's right "bounds" and using the one from the garbage information, whatever that might be!
So the solution was to have a flag at the top of the Proceed_click procedure that was set if I hit the Proceed button while still in Pause mode, and before actually processing the new Proceed click, would unwind all of those garbage calls by telling each returned-to procedure to exit immediately to ITS calling procedure...all the way back to the Proceed procedure. THEN the actual Proceed process would start again. The idle procedure looked like this:
And at the very top of my Proceed_click procedure was :Code:Private Sub Pause_Continue_cmd_Click()
'PauseFlag = 1, Continue
'PuaseFlag = -1, Pause
PauseFlag = -PauseFlag
If PauseFlag = 1 Then
Pause_Continue_cmd.Caption = "Pause"
Pause_Continue_cmd.BackColor = &HFFFFC0 'Light-Blue
Else
Pause_Continue_cmd.Caption = "Continue"
Pause_Continue_cmd.BackColor = &H8080FF 'Mid-Red
End If
EndlessLoop:
DoEvents
'THIS DECISION BLOCK IMMEDIATELY NEEDS TO FOLLOW ALL DoEvents LINES, AND
'AFTER ANY CALL STATEMENTS THAT JUMP TO PROCEDURES THAT CONTAIN DoEvents LINES:
If ReProceedFlag = 1 Then 'The "Proceed" button was clicked while in Pause mode.
Exit Sub
End If
If PauseFlag = 1 Then Exit Sub
GoTo EndlessLoop
End Sub
And just after that very first call in Proceed_click, was a special form of the If/Else block:Code:ReProceed:
If PauseFlag = -1 Then 'Presently still in Pause mode.
ReProceedFlag = 1
PauseFlag = 1
Pause_Continue_cmd.Caption = "Pause"
Pause_Continue_cmd.BackColor = &HFFFFC0 'Light-Blue
Exit Sub
End If
Not that that is elegant, necessarily, but it DID do the trick. But it means putting that little IF/Else block after every DoEvents line and all calls to procedures with DoEvents in them.Code:'THIS DECISION BLOCK IMMEDIATELY NEEDS TO FOLLOW ALL DoEvents LINES, AND
'AFTER ANY CALL STATEMENTS THAT JUMP TO PROCEDURES THAT CONTAIN DoEvents LINES:
'This particular block needs to be different because it occurs in the topmost calling procedure:
If ReProceedFlag = 1 Then 'The "Proceed" button was clicked while in Pause mode.
' Exit Sub
ReProceedFlag = 0
GoTo ReProceed
End If
Anyway...Loads of fun, not. So I learned a valuable lesson. Don't be lazy...Always include code to empty the stack of garbage, because there is no way in vb6 (except maybe via the API??) to empty out the stack with one statement.
-------------------
Jacob Roman, you JUST posted before I could get my post up.:) Do you have an idea how much faster the API method is. I've seen dramatic speed increases with the API, and this would certainly help in my situation.
I always use Doevents with DO & WHILE loops to prevent accidental infinite looping
But can't a Do loop fail the Until/While part just like a For loop fails the Next part? If any loop is re-entered with garbage data in the stack, wouldn't the effect be the same?Quote:
I always use Doevents with DO & WHILE loops to prevent accidental infinite looping
Jacob Roman > Please disregard my performance question, when I scrolled down through your code, I saw your performance figures. 14% faster is fairly substantial.
What I meant was:
If I "mess up" the code in a way that for some reason I think there might not be exit from the loop or the code inside the loops ends up "bloating",
DoEvents will only allow you to terminate the app with task manager for example.
At least this is how/why I use doevents most of the times.
But Im not even near close being a good programmer, so I'm just sayin'
Here is a contrived demo showing what Microsoft suggests for managing long-running loops in the VB6 manuals.
With the tuning parameter values in the code now, this is best tested after compiling to native code. If you want to compare in the IDE you will probably want to lower the "big loop" limit and tweak the parameters a bit.
I don't expect anyone to listen though, which is just fine. Doing things correctly is what we call competitive advantage out here where we code for money. :p
And if ayone has a more reliable, general solution I'd love to hear it. Especially something that works when you have an external event source you need to service such as a Winsock control, an MSComm control, MSMQ objects, SysInfo control, MCI control, async ADO Connection objects, HTTP Request objects, Inet controls, etc.
That statement right there is what is wrong with people using DoEvents... a simple lack of fully understanding what it does and why it can be detrimental. The short answer to your question is that the counter check is less of a performance hit. And here's why:
When you call DoEvents, you actually momentarily SUSPEND your thread (perf hit #1) so that the system can check (perf hit #2) to see if there are any other pending messages in the queue, and if so allow them to be processed (perf hit #3 and so on)... A common reason people use DoEvents is to allow their screen to remain responsive while stuck in a loop doing something. Which is fine... in VB6 it's the only way... well at the least it's the most commonly used way, you can subclass in VB6, but it's a bit more work. At any rate, my point is that doing a simple compare (if then) is less intrusive than automatically calling DoEvents each time. The trick is finding out the right number to count to before calling it. I try to divide my loops in to equal segments, depending on the size. If I don't know the size, I usually start with a factor of 10.
-tg
Checking involves a relatively simple If statement, which is almost the fastest code there is... DoEvents does so many different (and "unpredictable") things that is near the other end of the scale.
In my experience the difference is usually less than 1%, so I guess that as with many things the speed varies based on the circumstances.Quote:
Jacob Roman > Please disregard my performance question, when I scrolled down through your code, I saw your performance figures. 14% faster is fairly substantial.
There's no need to work out a factor (which varies when compiled, or when you add/alter code, etc), just do it based on something time instead, eg:
This is untested, but is very similar to what I normally use with DoEvents.Code:Public Sub DoEventsIfApt
Static NextTime as Single
Dim CurrentTime as Single
CurrentTime = Timer
If (CurrentTime < NextTime) Then
If Not (CurrentTime + 10 > NextTime) Then 'deal with Timer 'wrapping' at midnight
Exit Sub
End If
End If
DoEvents
NextTime = Timer + 0.2 'the number of seconds gap (don't normally need to change it)
End Sub
Having said all of that however, dilettante's post contains a very good alternative for many situations.
Honestly, I never thought about making it time-based. That's certainly something I'll have to remember. The last time I needed to use it was in processing some data that was coming back from a web service in chunks, and we knew the size of the chunks (500 records at a time), so it was easy to put a .count mod 50 = 0 check and run DoEvents which then resulted in acceptable performance for hte data and responsiveness of the UI (the users wanted to see their data loading in chunks, and yet a progress bar or wait symbol was out of the question)
Given that the connection could be via LAN, WAN, or even SatLAN, a time-based interval may not have worked in that case.
-tg
The time-based method I showed isn't a perfect solution, but it limits the minimum frequency that DoEvents can run (in my example, at least 0.2s after the previous).
If the gap is larger (due to network delays etc), it will run immediately. Admittedly that could mean that just the first record shows to start with, then the next 51 or whatever... in cases like that I generally set NextTime just before the loop.
Using those two variations of the idea, I have yet to find a situation where it isn't at least equal to the Mod idea - and it takes no effort to work out (or guess) the frequency you want.
I agree you can write the logic to "self tune" for a good balance between responsiveness and workload completion rate either using the "occasional DoEvents" or a Timer approach. You still end up with a few constants to jigger but at least once you have those set the code will accommodate itself to variations in CPU speeds, environment, and overall system workload or other limiting factors.
And there are a few safe ways to have a background worker thread or separate process.
Cool ideas!
Are you referring to vb.net and multi-threading, or actually running an external process that the <= vb6 program communicates with? Or something else, maybe?Quote:
And there are a few safe ways to have a background worker thread or separate process.
There are ways to use an ActiveX EXE to host additional threads via the Thread Per Object setting, though it gets complicated. There are a few 3rd party libraries that provide a way to allocate and run worker threads in a VB6 program as well. Most of these are complicated and unsatisfactory enough to make them rarely used though.
Yes, you can create a Form-less VB6 program to act as a worker process. You can either start it using the Shell() command and use variations on shell-and-wait to detect when it has completed (polling using a Timer works best). You can also use CreateProcess to run it, and this allows StdIO redirection through anonymous pipes so your main program has a way to "talk to it" instead of using something like disk files to pass data into it and back from it. Or you might use something hackish like Winsock controls, or clean and fast like Named Pipes to talk between them.
None of this is easy enough to be valuable for general use though. You only go to the effort in specialized cases that leave you little choice.
Most of those options don't give you "workers" that can see the global data in the "main" thread, which is why you have to ship data around. Even if they did, you'd now have to deal with locks and synchronization so they didn't step on each other.
Still a neat idea to experiment with though...The idea of making vb6 multi-threading, if in a convoluted way.
Should be some sample code for different approaches in the CodeBank.
It would be absolutely necessary in vb6 for programs that really DO need a speed increase from multi-threading. I had a program once that calculated the position and velocity of a spacecraft on its way from someplace in the Solar System to any other place in it. Where the parallel-ness (new word? :)) was needed was calculating each planet's position and velocity at each time step, so that all of that info could be brought together to calculate the spacecraft's state vector. Otherwise, it was too damn slow. I had to port it over to .net to do that, because I never thought of trying to get multithreads into vb6. And I REALLY like vb6 a ton better than .net. Especially with the "easy" access vb6 has to the API. I would be much faster at the API approach if I had the opportunity to use it more often.
I think any time that a finite element method is required, multi-threading in vb6 would be a must.
I'll add My2Cents. I "rarely" use them even for loops. Instead I use a "State Machine"
Originally got this idea from the article:
Would post article, but not sure forum rules will allow and also as I recall can't post a PDF file.Quote:
Design True Event-Driven Code
by BY DANIEL APPLEMAN
AUGUST/SEPTEMBER 1994 Visual Basic Programmer’s Journal
State-machine theory can help you design for an
event-driven environment and avoid problems relating to
reentrancy and user interaction.
I always use this when using DoEvents.
I only use it if really needed for the app to respond with user's input.Code:Private Declare Function GetInputState Lib "user32" () As Long
If GetInputState Then DoEvents
When using the: "DoEvents" command, use it sparingly. You only have to use it, when you are running many processes together that are running alongside eachother. Like a very long winded Timer handler, or even something else like a Winsock Connection, as such...
Here is a demo using a free 3rd party library that helps you do multithreading in VB6. Be sure to go through the README file.
It is about the smallest practical example I could think of.
Hm...Darn cool. Thanks for the link. A must-see.
Question: So you're trying to multi processor support a VB6 app, right???
Well, actually the multiproc thing developed out of this thread as an expansion of what I originally posted. I will stick with DoEvents on this project, but I am VERY interested in using multiprocessing in vb6 for programs that use the Finite Element Method, or any numerical problem that can be broken up into parallel tasks.
-- Try Apartment Threading, which is in the Project Properties DialogBox, on the VB6 MenuBar???
Ps: Apartment Threading = Some sort of Hardware/Software Multiprocessor Processes. (Processor must be Multi Core or even Muti Processor, to support this. If not, then cannot run the Project!! Danger Will Robertson, Danger...
When I go there, the thread controls are greyed out. Is that because I need to load a reference or component or something?Quote:
-- Try Apartment Threading, which is in the Project Properties DialogBox, on the VB6 MenuBar???
Do you have Dual/Tri/Quad Core Pentium IV??? Or even an: i3/i5/i7 type Processor??? You must have one of those, to do that. Like I said, in my last post. You have to read the whole post, because there is a lot of information in them!!
Yes, I have an EX58-Extreme 3.2GHz, quad core, 8 threads.
If you are referring to the info in the links you provided in the last post, they come up, page not found.:(
I think you might be a little lost. See the manual: Apartment-Model Threading in Visual Basic.
This is what all VB6 programs use. It has nothing to do with hardware, being a software concept native to COM.
If I'm understanding my quick scan of the article, it looks like a lot of the debugging has to be via an external debugger, which I assume means memory and register dumps and such. If so, that reminds me of my old assembly days!
If you are using Visual Basic 6, in the COM section. Then I suggest Application Preformance Manager, for the Enterprise Edition...
You can probably debug the bulk of your logic without using multithreading. Just add that after the application logic has been stabilized. Then you can debug the threading and concurrency issues.
Most people prefer to use an approach based on threading in a secondary ActiveX EXE though. No 3rd party library required then, but you still have the debugging issues.
There are other techniques that delegate "background" operations to a second Form-less standard EXE. Usually you need some form of IPC to pass data back and forth as well as to signal completion. There are plenty of IPC choices available. This avoids the need for any 3rd party libraries and avoids ActiveX EXE registration requirements - normally only a big issue for portable applications (applications with no formal installer).
Please remember that the term: Debugging, doesn't mean the command process: Debug.Print, so forget that in this process...
Also please note: That I haven't slept for about thirty seven hours, then my spelling it getting a little off. So please correct me, I have been editing this thread post, of mine for a long time!!