繁体   English   中英

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

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

因此,在此之后 ,我决定在专用的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的答案的某些部分现在似乎无关紧要,但它们是针对原始问题的。

请记住,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;
}

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM