That depends on what you really need.
In my case I created a class (ActionRunner... and later a RunnableRunner). I use a Thread from my own pool of threads. It waits for a new Action (using a ManagedAutoResetEvent) and uses a Queue to effectively queue the Actions.
So, while there are items in the queue they are executed. When there are no more, an wait is done. My own ActionRunner is disposable, but that's to guarantee that I free the thread at the right moment. I consider it the best solution as it does not creates an unnecessary window and because the messages are real .Net actions (and not some number that must be processed by a switch).