2014-02-10 3 views
11

Итак, после this, я решил явно создать экземпляр COM-объекта в выделенном потоке STA. Эксперименты показали, что объект COM нужен сообщение насос, который я создал по телефону Application.Run():Как отправлять сообщения в поток STA с помощью насоса сообщений?

private MyComObj _myComObj; 

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

Как отправлять сообщения в сообщении насоса ГЕН поток из других потоков?

Примечание: Я в значительной степени отредактировал этот вопрос для краткости. Некоторые части ответа @ Servy теперь кажутся несвязанными, но они были для оригинального вопроса.

+0

Для неблокирующего инициирования вы не можете использовать ThreadPool.QueueUserWorkerItem? – Didaxis

+0

@Didaxis, нет, потому что тогда насос сообщений не работает в этой нити. – Servy

+0

[Этот ответ] (http://stackoverflow.com/a/21775343/1768303) использует TPL и 'async/await' для реализации и вызова в квартиру STA. – Noseratio

ответ

16

Имейте в виду, что очередь сообщений, которая создает для Windows для STA thread уже является реализацией потокобезопасной очереди. Поэтому просто используйте его для своих целей. Вот базовый класс, который вы можете использовать, чтобы получить свой собственный объект 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; 
} 
+0

[Здесь] (http://stackoverflow.com/a/21460107/499721) вы упомянули, что разрешение COM сделать маршалингом обычно примерно в 10000 раз медленнее, чем прямой вызов самого сообщения. Будет ли решение, которое вы опубликовали, быстрее, чем позволить COM сделать маршалинг? Если да, то почему COM занимает больше времени, чтобы в основном отправлять сообщения в очередь сообщений? – bavaza

+1

Вы создали поток STA, поэтому метод вызывает COM-объект **, а не ** должен быть маршалирован. Не знаю, что такое BlockingQueue, но если вы торгуете, что для вызовов Begin/Invoke, то нет, это вряд ли может иметь большое значение. По крайней мере, вы можете выполнить кучу кода с помощью одного вызова и избежать смерти тысячами игл. –

+0

@HansPassant: Вы видите какие-либо проблемы с созданием нескольких экземпляров этого класса в моем приложении? – dotNET

2

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

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

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

Спасибо Servy.Я все еще озадачен, хотя, если я понимаю ваше решение, оно отправляет действия на насос сообщений, созданный при вызове 'Application.Run()'. Как это отличается от того, чтобы позволить маршалу COM звонить мне (с ударом производительности)? Более того, во многих местах упоминается неявная накачка COM-сообщений, когда поток заблокирован (например, «Thread.Join'). Как был создан этот неблокирующий насос сообщений? – bavaza

+0

@bavaza 'Thread.Join' не будет накачивать любые сообщения. Он просто ничего не сделает, пока не закончится нить. – Servy

+0

из MSDN: 'Thread.Join()' - "Блокирует вызывающий поток до тех пор, пока поток не завершится, продолжая выполнять стандартную передачу COM и SendMessage." – bavaza

Смежные вопросы