In my projects, I've been using various Game State Management schemes generally derived from the pretty good XNA Game State Management Sample. (The rest of this post assumes you're familiar with that particular implementation) Now, this sample incorporates a loading screen, which does the trick, but it is not animated; it does not launch a separate thread to load on while giving some visual feedback that the machine hasn't locked up. Telling your users that you're loading is only about half the point: indicating progress or at least animating something to tell them you haven't locked up/died is the other half.
I've been thinking a bit about this problem lately, and I think the solution may be to redesign the GSM system to always assume a new screen is going to load slowly, therefore always showing the loading screen. The base GameScreen class should be placed in a loading state: the screen we are transitioning away from flips from it's 'normal' state into a 'loading' state, and waits til the load is complete to actually transition off. With the base GameScreen just specifying the state, inheriting screens can customize how they display the loading on a per screen basis, or a intermediate class could be defined that just defines how loading is displayed in that particular game and all leaf node screens inherit from that screen. This makes for a very easy and clean way to make sure loading works the same way across the entire application.
Screens that load quickly will probably just blink from one to another without really displaying the graphic, and ones that are slow will automatically display the graphic. If the transition is bumpy, one could set a flag (loadingIsSlow = true) to indicate to use the second thread to load and show the loading screen.
The issues of XNA's GraphicsDevice being thread safe need to be addressed, but that's a different post.