¿Cómo publicar mensajes en un hilo STA que ejecuta una bomba de mensajes?

Entonces, después de esto , decidí crear una instancia explícita de un objeto COM en un hilo STA dedicado. Los experimentos mostraron que el objeto COM necesitaba una bomba de mensajes, que creé llamando a Application.Run() :

 private MyComObj _myComObj; // Called from Main(): Thread myStaThread = new Thread(() => { _myComObj = new MyComObj(); _myComObj.SomethingHappenedEvent += OnSomthingHappened; Application.Run(); }); myStaThread.SetApartmentState(ApartmentState.STA); myStaThread.Start(); 

¿Cómo publico mensajes el mensaje de bomba del mensaje STA desde otros hilos?

Nota: He editado en gran medida la pregunta en aras de la brevedad. Algunas partes de la respuesta de @Servy ahora parecen no estar relacionadas, pero fueron para la pregunta original.

    Tenga en cuenta que la cola de mensajes que crea Windows para un subproceso STA ya es una implementación de una cola segura para subprocesos. Así que solo úsala para tus propios fines. Aquí hay una clase base que puede usar, derive la suya para incluir su objeto COM. Anula el método Initialize (), se llamará tan pronto como el hilo esté listo para comenzar a ejecutar el código. No olvides llamar a base.Inicializar () en tu anulación.

    Si desea ejecutar código en ese hilo, utilice los métodos BeginInvoke o Invoke, tal como lo haría para los métodos Control.Begin / Invoke o Dispatcher.Begin / Invoke. Llame a su método Dispose () para cerrar el hilo, es opcional. Tenga en cuenta que esto solo es seguro cuando está 100% seguro de que todos los objetos COM están finalizados. Como normalmente no tienes esa garantía, es mejor que no lo hagas.

     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; } 

    ¿Hay alguna manera de iniciar la bomba de mensajes para que no se bloquee?

    No. El punto de una cola de mensajes es que necesita consumir la ejecución del hilo. Una cola de mensajes es, en implementación, muy similar a su:

     while(!_stopped) { var job = _myBlockingCollection.Take(); // < -- blocks until some job is available ProcessJob(job); } 

    Eso es un ciclo de mensajes. Lo que intenta hacer es ejecutar dos ciclos de mensajes diferentes en el mismo hilo. Realmente no se puede hacer eso (y hacer que las dos colas funcionen, una cola, por necesidad, pausará la ejecución de la otra mientras se está ejecutando), simplemente no tiene sentido.

    Lo que debe hacer, en lugar de crear un segundo bucle de mensaje en el mismo hilo, es enviar mensajes a su cola existente. Una forma de hacerlo es mediante el uso de un SynchronizationContext . Sin embargo, un problema es que no hay ningún evento que pueda engancharse para ejecutar un método en la bomba de mensajes con esa sobrecarga de Run . Tendremos que mostrar un Form solo para que podamos conectarnos al evento Shown (en cuyo caso podemos ocultarlo). Luego podemos tomar SynchronizationContext y almacenarlo en algún lugar, permitiéndonos usarlo para publicar mensajes en la bomba de mensajes:

     private static SynchronizationContext context; public static void SendMessage(Action action) { context.Post(s => action(), null); } 

     Form blankForm = new Form(); blankForm.Size = new Size(0, 0); blankForm.Shown += (s, e) => { blankForm.Hide(); context = SynchronizationContext.Current; }; Application.Run(blankForm);