2014-02-10 5 views
11

Also, nach this, entschied ich mich, explizit ein COM-Objekt in einem dedizierten STA-Thread instanziieren. Die Experimente zeigten, dass das COM-Objekt eine Nachricht Pumpe benötigt, die ich durch den Aufruf Application.Run() erstellt:Wie können Nachrichten an einen STA-Thread gesendet werden, der eine Nachrichtenpumpe ausführt?

private MyComObj _myComObj; 

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

Wie schreibe ich Nachrichten die das Nachrichtensystem von anderen Threads des STA-Thread?

Hinweis: Ich habe die Frage aus Gründen der Kürze stark bearbeitet. Einige Teile von @ Servys Antwort scheinen jetzt nicht verwandt zu sein, aber sie waren für die ursprüngliche Frage.

+0

Für eine nicht blockierende Initiierung können Sie ThreadPool.QueueUserWorkerItem nicht verwenden? – Didaxis

+0

@Daxis, nein, weil dann die Nachrichtenpumpe in diesem Thread nicht läuft. – Servy

+0

[Diese Antwort] (http://stackoverflow.com/a/21775343/1768303) verwendet TPL und 'async/await', um eine STA-Wohnung zu implementieren und anzurufen. – Noseratio

Antwort

16

Beachten Sie, dass die Nachrichten-Warteschlange, die Windows für eine STA erstellt Thread ist bereits eine Implementierung einer Thread-sicheren Warteschlange. Benutze es einfach für deine eigenen Zwecke. Hier ist eine Basisklasse, die Sie verwenden können, leiten Sie Ihre eigenen ab, um Ihr COM-Objekt einzuschließen. Überschreiben Sie die Initialize() -Methode, sie wird aufgerufen, sobald der Thread zum Ausführen von Code bereit ist. Vergessen Sie nicht, base.Initialize() in Ihrer Überschreibung aufzurufen.

Wenn Sie Code für diesen Thread ausführen möchten, verwenden Sie die Methoden BeginInvoke oder Invoke genau wie für die Methoden Control.Begin/Invoke oder Dispatcher.Begin/Invoke. Rufen Sie die Dispose() -Methode auf, um den Thread herunterzufahren. Dies ist optional. Beachten Sie, dass dies nur sicher ist, wenn Sie 100% sicher sind, dass alle COM-Objekte finalisiert sind. Da Sie diese Garantie normalerweise nicht haben, ist es besser, dass Sie das nicht tun.

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

[Hier] (http://stackoverflow.com/a/21460107/499721) Sie erwähnten, dass COM das Marshalling ist in der Regel etwa 10000 mal langsamer als ein direkter Anruf auf die Nachricht selbst. Ist die von Ihnen gepostete Lösung schneller, als dass COM das Marshalling ausführen kann? Wenn ja, wie kommt es, dass COM länger braucht, um in die Nachrichtenwarteschlange zu gelangen? – bavaza

+1

Sie haben einen STA-Thread erstellt, sodass die Methode das COM-Objekt ** not ** aufruft, das gemarshallt werden muss. Keine Ahnung, worum es bei der BlockingQueue ging, aber wenn Sie das für Begin/Invoke-Aufrufe gehandelt haben, dann nein, das macht wahrscheinlich keinen großen Unterschied. Zumindest können Sie eine Menge Code mit einem einzigen Aufruf ausführen und den Tod durch tausend Nadelstiche vermeiden. –

+0

@HansPassant: Sehen Sie Probleme beim Erstellen mehrerer Instanzen dieser Klasse in meiner Anwendung? – dotNET

2

Gibt es eine Möglichkeit, die Nachrichtenpumpe zu starten, damit sie nicht blockiert?

No. Der Punkt eine Nachrichtenwarteschlange ist, dass es den Threads der Ausführung verbrauchen muss. Eine Nachrichtenwarteschlange, in Implementierung, geht sehr ähnlich wie Ihre aussehen:

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

Die ist eine Nachrichtenschleife. Sie versuchen, zwei verschiedene Nachrichtenschleifen im selben Thread auszuführen. Sie können das nicht wirklich tun (und beide Warteschlangen pumpen; eine Warteschlange wird notwendigerweise die Ausführung der anderen pausieren, während sie läuft), es macht einfach keinen Sinn.

Was Sie tun müssen, anstatt eine zweite Nachrichtenschleife in demselben Thread zu erstellen, ist das Senden von Nachrichten an Ihre vorhandene Warteschlange. Ein Weg, dies zu tun, ist durch die Verwendung eines SynchronizationContext. Ein Problem besteht jedoch darin, dass es keine Ereignisse gibt, die zum Ausführen einer Methode in der Nachrichtenpumpe mit der Überladung von Run angehängt werden können. Wir müssen nur eine Form zeigen, damit wir in das Ereignis Shown einhaken können (an dem Punkt können wir es verstecken). Wir können dann die SynchronizationContext packen und es irgendwo speichern, so dass wir es verwenden, um Nachrichten an das Nachrichtensystem zu schreiben:

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

Danke Servy.Ich bin jedoch immer noch verwirrt - wenn ich Ihre Lösung verstehe, ist es das Posten von Aktionen auf die Nachrichtenpumpe, die durch den Aufruf von 'Application.Run()' erstellt wurde. Wie unterscheidet sich das davon, COM die Anrufe für mich marschieren zu lassen (mit Performance-Hit)? Außerdem erwähnen viele Stellen das implizite Pumpen von COM-Nachrichten, während der Thread blockiert ist (z. B. "Thread.Join"). Wie wurde diese nicht blockierende Nachrichtenpumpe erstellt? – bavaza

+0

@bavaza 'Thread.Join' wird keine Nachrichten pumpen. Es wird nur nichts tun, sondern warten, bis der Thread fertig ist. – Servy

+0

von MSDN: 'Thread.Join()' - "Blockiert den aufrufenden Thread, bis ein Thread beendet wird, während das standardmäßige COM- und SendMessage-Pumpen fortgesetzt wird." – bavaza

Verwandte Themen