How to post messages to an STA thread running a message pump?

Keep in mind that the message queue that Windows creates for an STA thread is already an implementation of a thread-safe queue. So just use it for your own purposes. Here’s a base class that you can use, derive your own to include your COM object. Override the Initialize() method, it will be called as soon as the thread is ready to start executing code. Don’t forget to call base.Initialize() in your override.

It you want to run code on that thread then use the BeginInvoke or Invoke methods, just like you would for the Control.Begin/Invoke or Dispatcher.Begin/Invoke methods. Call its Dispose() method to shut down the thread, it is optional. Beware that this is only safe to do when you are 100% sure that all COM objects are finalized. Since you don’t usually have that guarantee, it is better that you don’t.

using System;
using System.Threading;
using System.Windows.Forms;

class STAThread : IDisposable {
    public STAThread() {
        using (mre = new ManualResetEvent(false)) {
            thread = new Thread(() => {
                Application.Idle += Initialize;
                Application.Run();
            });
            thread.IsBackground = true;
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            mre.WaitOne();
        }
    }
    public void BeginInvoke(Delegate dlg, params Object[] args) {
        if (ctx == null) throw new ObjectDisposedException("STAThread");
        ctx.Post((_) => dlg.DynamicInvoke(args), null);
    }
    public object Invoke(Delegate dlg, params Object[] args) {
        if (ctx == null) throw new ObjectDisposedException("STAThread");
        object result = null;
        ctx.Send((_) => result = dlg.DynamicInvoke(args), null);
        return result;
    }
    protected virtual void Initialize(object sender, EventArgs e) {
        ctx = SynchronizationContext.Current;
        mre.Set();
        Application.Idle -= Initialize;
    }
    public void Dispose() {
        if (ctx != null) {
            ctx.Send((_) => Application.ExitThread(), null);
            ctx = null;
        }
    }
    private Thread thread;
    private SynchronizationContext ctx;
    private ManualResetEvent mre;
}

Leave a Comment