Sorry for the silence -- I was just reminded of this question and thought I'd answer it myself.
I was able to largely solve the problem by creating a progress bar window that appears while the main Visual Studio thread is off running my code and not updating the UI. The progress bar (IVsThreadedWaitDialog2) is very poorly documented, but it appears to run on a worker thread and puts Visual Studio into a modal state while active. So that, at least, keeps Visual Studio from looking like it's hung during during processing.
The progress bar window is the same one that appears when Visual Studio is loading a large solution and has the option of a Cancel button.
Unfortunately, the progress bar dialog has several bugs (reported to Microsoft), the most significant of which is that it often appears behind the Visual Studio window -- truly annoying. That's kind of a show-stopper, so whenever the progress bar is updated, I use a variation of the code here to locate the progress window by its title and bring it to the top of the Visual Studio window stack.
Moving the processing to a worker thread didn't work very well, as it executes shared code that manipulates text in the Visual Studio editor, and the results of those manipulations were erratic (sometimes it would work, sometimes an inscrutable COM error was returned, which seemed to be timing-dependent).
I also did finally find a message pump in Visual Studio -- the shell contains a class called CommonMessagePump. Again, it is very poorly documented and I could never get it working quite right based on the information that was available. In any case the progress dialog has solved my problem.