As jo0ls said, it is probably technically
better to release your worksheet object when you are done with it and still have a reference to it, before moving on to the next object.
That said, as a practical matter, I find this style of coding to be way too cumbersome, and you are likely to forget to do it somewhere anyway at some point -- maybe lots of points!
In my own coding, I do not bother releasing each and every object as I use them and I never get a hang. Within your cleanup/shutdown section, the key is, to call GC.Collect() and then GC.WaitForPendingFinalizers() before
releasing the objects to which you do have a reference, and then release your remaining objects via Marshal.FinalReleaseComObject() in order from least important object to the most important (for example, Range objects first, then Worksheets, then Workbooks, and then finally your Application object).
Take a look at the two posts that jo0ls listed, which go into how to do this in detail. I think that the StackOverflow Article on Cleaning up COM Objects
is probably the most on point here, but also read the Automating Office Programs with VB.NET
article that jo0ls mentioned.
If you follow this approach, your code can be a lot cleaner and you shoudn't have any hangs.