如何将消息发布到运行消息泵的STA线程?

所以,在这之后 ,我决定在一个专用STA线程上显式实例化一个COM对象。 实验表明,COM对象需要一个消息泵,我通过调用Application.Run()创build消息泵:

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

我如何从其他线程发布消息STA线程的消息泵?

注意:为了简洁起见,我大量地编辑了这个问题。 现在@Servy的答案的一些部分似乎是无关的,但他们是原来的问题。

请记住,Windows为STA线程创build的消息队列已经是线程安全队列的实现。 所以就把它用于你自己的目的。 这里有一个你可以使用的基类,派生自己的COM对象。 重写Initialize()方法,只要线程准备好开始执行代码,它就会被调用。 不要忘记在你的覆盖中调用base.Initialize()。

它想要在该线程上运行代码,然后使用BeginInvoke或Invoke方法,就像使用Control.Begin / Invoke或Dispatcher.Begin / Invoke方法一样。 调用Dispose()方法closures线程,这是可选的。 当你100%确定所有的COM对象都已经完成的时候,请注意这样做是安全的。 既然你通常没有这个保证,那么你最好不要。

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

有没有办法启动消息泵,所以它不会阻止?

不。消息队列的要点是它需要消耗线程的执行。 消息队列在实现中看起来与你的非常相似:

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

一个消息循环。 你要做的是在同一个线程中运行两个不同的消息循环。 你真的不能这么做(并且同时拥有两个队列,一个队列在必要的时候会暂停另一个队列的运行),这是没有意义的。

你需要做的,而不是在同一个线程上创build第二个消息循环,是发送消息到您现有的队列。 一种方法是通过使用SynchronizationContext 。 然而,一个问题是,没有任何事件可以被挂钩到在消息泵中执行Run超载的方法。 我们将需要显示一个Form ,以便我们可以钩入Shown事件(在这一点上,我们可以隐藏它)。 然后我们可以获取SynchronizationContext并将其存储在某个地方,从而允许我们使用它将消息发布到消息泵:

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