因此,在this之后,我决定在专用的STA线程上显式实例化一个COM对象。实验表明,COM对象需要一个消息泵,我通过调用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();
如何从其他线程发布STA线程的消息泵消息?
注意: 为了简洁,我大量编辑了这个问题。 @Servy的答案的某些部分现在似乎无关,但它们是原始问题。
答案 0 :(得分:20)
请记住,Windows为STA线程创建的消息队列已经是线程安全队列的实现。所以只需将它用于您自己的目的。这是一个可以使用的基类,派生自己的基类来包含COM对象。重写Initialize()方法,一旦线程准备开始执行代码,就会调用它。不要忘记在覆盖中调用base.Initialize()。
您希望在该线程上运行代码,然后使用BeginInvoke或Invoke方法,就像您对Control.Begin / Invoke或Dispatcher.Begin / Invoke方法一样。调用它的Dispose()方法来关闭线程,它是可选的。请注意,当您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;
}
答案 1 :(得分:3)
有没有办法启动消息泵,因此它不会阻塞?
没有。消息队列的 point 是它需要消耗线程的执行。在实现中,消息队列看起来与非常类似于:
while(!_stopped)
{
var job = _myBlockingCollection.Take(); // <-- blocks until some job is available
ProcessJob(job);
}
是一个消息循环。你要做的是在同一个线程中运行两个不同的消息循环。你不能真的这样做(并且两个队列都在抽水;一个队列必然会在另一个队列运行时暂停执行),它只是没有意义。
您需要做的是将消息发送到现有队列,而不是在同一个线程上创建第二个消息循环。一种方法是使用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);