Simple -- it monkeys with the natural order of things.
When you call App.DoEvents, a handful of big, scary things happen. First, a single pass of the event loop is called. This can cause drastically odd things to happen with your GUI application. You're sitting in an event somewhere (because all code is an event with GUI applications) and you call DoEvents. Another pass of the event loop happens, and suddenly you may very well have re-entered the exact event you're already in! Or worse yet, you enter *another* event and access things you expected to be already set up, but they're not since the setup code happens after the call to DoEvents. For example, it's possible that you call DoEvents from the Window.Resizing event, and that causes the Window.Resized event to fire. Yikes!
But wait! It's not over yet. After you're done mucking with the natural order of the event loop, it yields the time slice of the current thread! If you passed in a number (like DoEvents(50)), and the application only contains one thread (the main thread), then the application goes to sleep for that number of milliseconds.
So not only are you causing the entire event loop to run one iteration, you're yielding time back to another thread in the process.
So when is it OK to use DoEvents? If you're a GUI application, the answer is never. However, things are a lot safer for console applications (and that's the entire reason for the function).
A console application has no event loop (by default), and so all the re-entrancy issues go away. Your application enters the run event, and once it exits, it terminates. No event loop. So then how do you cause your application to live forever, such as an interactive application might do? With a loop in the Run event. And this worked great for a long while, until someone pointed out that the CPU was being tanked due to tight loops. This was mostly evident in networked console applications such as echo servers. So we made App.DoEvents as a way for you to not only poll sockets (and the like), but yield time back to the OS so the CPU doesn't peak at 100%. In a console application, DoEvents is perfectly safe (except for one big gotcha).
App.DoEvents can wreak havoc with multithreaded applications. In fact, it is possible for you to completely deadlock your application or overflow the stack and crash with no possible way to recover. Mars explained it in the most eloquent way:
DoEvents is *not* just a yield function. It does yield to other threads, but it also runs the event loop. If you call it from threads other than the main thread, you will end up splitting your main event loop across multiple threads. If you are unlucky, this will not cause any problems right away, and you will think your program works. Then one of your users will manage to get the timing just right and your app will wind itself up with one DoEvents call inside another until your stack overflows.
So here's the definitive list of when it's safe to use DoEvents: From the main thread of a console application. That's the whole list.
What drives me crazy is the statement (that goes something like this): "But I've used it in situation X and it solved my issue". Yeah -- until Mars and Jupiter align correctly, and a butterfly flaps its wings in Bolivia at which point your application crashes for no discernable reason. :: sighs :: People need to understand that DoEvents in GUI applications that "work" are working by chance -- not by design.
What is the proper way to yield to other threads without calling App.DoEvents, in a GUI application?
In RB 2005: App.YieldToNextThread
In earlier versions: There is no sanctioned way.
Threads have pre-defined places at which they will yield automatically (after ending an event, during a for/while loop, etc), so you generally don't need to yield manually. However, for those times when you do, we added a safe way to do it.