Как отправлять сообщения в поток STA, работающий с насосом сообщений?

Итак, следуя этой, Я решил явно создать экземпляр COM-объекта в выделенном потоке STA. Эксперименты показали, что 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 теперь кажутся несвязанными, но они были для первоначальный вопрос.

2 ответов


имейте в виду, что очередь сообщений, создаваемая Windows для потока STA, уже является реализацией потокобезопасной очереди. Так что просто используй его для своих целей. Вот базовый класс, который вы можете использовать, производите свой собственный, чтобы включить свой COM-объект. Переопределите метод Initialize (), он будет вызван, как только поток будет готов начать выполнение кода. Не забудьте позвонить.Initialize () в вашем переопределении.

Если вы хотите запустить код в этом потоке, используйте BeginInvoke или Invoke методы, как и для элемента управления.Begin / Invoke или Dispatcher.Методы Begin / Invoke. Вызовите его метод Dispose (), чтобы закрыть поток, это необязательно. Будьте осторожны, что это безопасно делать только тогда, когда вы на 100% уверены, что все COM-объекты завершены. Так как у вас обычно нет такой гарантии, то лучше ее не давать.--2-->

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

есть ли способ запустить насос сообщений, чтобы он не блокировался?

нет. The точка очереди сообщений является то, что он должен потреблять потока. Очередь сообщений в реализации будет выглядеть очень похож на ваш:

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

это is цикл обработки сообщений. То, что вы пытаетесь сделать, это запустить два разных цикла сообщений в одном потоке. Вы не можете этого сделать (и иметь обе очереди перекачивают; одна очередь, по необходимости, приостанавливает выполнение другой, пока она работает), это просто не имеет смысла.

вместо создания второго цикла сообщений в том же потоке вам нужно отправить сообщения в существующую очередь. Один из способов сделать это с помощью 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);