2011-06-22 84 views
1

我有一個調用COM組件的c#靜態函數。從.Net調用COM調用掛起線程

當從一個普通的WPF應用程序調用此函數時,它會正確返回。 的代碼可能是這個樣子:

var result = MyClass.StaticCall(); 
Debug.WriteLine("Hurrah!"); 

當我打電話的代碼,該變量將被設置和調試消息輸出符合市場預期。

但是,如果我在一個線程中包裝相同的調用,它永遠不會返回。失敗的代碼可能如下所示:

var foo = new Thread(new ThreadStart(() => 
           { 
            var result = MyClass.StaticCall(); 
            Debug.WriteLine("Hurrah!"); 
           })); 
foo.Start(); 

while (foo.IsAlive) 
{ 
} 

在這種情況下,StaticCall將不會返回並且線程無限期地被阻塞。

什麼可能導致此行爲?

附加信息:

  • 設置線程的公寓狀態沒有什麼區別。
  • visual studio輸出窗口中的最後一條消息是COM互操作已加載的通知。
  • 所有對COM的調用都被隔離在一個線程中。
+0

* foo.Start()之後的代碼是什麼?這很重要。主線程不會閒置時,死鎖很常見。 –

+0

@Hans我編輯了這個問題來添加這個細節。 –

回答

1
foo.Start(); 

while (foo.IsAlive) 
{ 
} 

是的,這是保證死鎖。 COM對象通常具有線程關聯性。他們可以告訴COM他們不是線程安全的。絕大多數不是,就像.NET類一樣。 COM然後負責以線程安全的方式調用它們。與.NET不同,它完全由您提供線程安全。

您在主線程上創建了COM對象。因此,COM必須在主線程上調用COM對象以做出安全保證。它不能這樣做,你的主線程很忙。循環在foo.IsAlive屬性上。在主線程閒置之前,任何對工作線程的調用都無法完成。主線程在工作線程完成之前不能空閒。僵局城市。

一個補丁修復的是:

foo.Start(); 
foo.Join(); 

雖然的Thread.join()是一個阻塞調用,以及,它的實際工作。 CLR實現它,它知道阻塞STA線程是非法的。它抽取一個消息循環,允許COM調用被封送,並允許線程完成。

那麼,這可能不是你想要做的,你正在做的循環內的東西。唯一可以做的其他事情是調用Application.DoEvents(),它也會抽取消息循環。這很危險,您必須禁用用戶界面,以便用戶無法關閉主窗口並且無法再次啓動線程。

這是另外的沒有免費午餐的原則,你不能神奇地使代碼不是線程安全支持線程。沒有併發性,COM對象方法仍然在主線程上運行。如果他們花了很長時間,他們仍然會凍結用戶界面。

這導致另一個解決方法,而是在您的工作線程上創建COM對象。它必須是STA,通常需要泵送消息循環。

+0

因此,用foo.Join()替換while循環解決了我的例子中的問題,但我注意到,就像Jalal一樣,您提到了在工作線程上創建COM對象。我的印象是我已經這樣做了。鑑於StaticCall創建COM對象並調用它,是什麼讓你認爲COM對象正在主線程上創建?也許我缺少一個基本點? –

+0

我在代碼片段中沒有看到* new *語句。沒有足夠的代碼,我不知道爲什麼COM需要消息循環來編組。顯然它確實如此。使用Debug + Windows +線程在線程之間切換並查看工作人員被阻止的位置。 –

3

我似乎想起了一些關於COM需要在使用COM互操作的線程中運行的活動消息循環。我不知道.NET的COM互操作實現的細節,但是如果你在完成所有特殊的初始化步驟之後嘗試在COM上進行進程間調用,它仍然會在COM調用中凍結,就像你描述的那樣。添加一個簡單的peekmessage循環到後臺線程,並且調用會通過。也許類似的需要在你的.NET代碼中完成?

背景:COM進程間通信依賴於通過COM創建的窗口句柄的SendMessage。如果該窗口句柄是在主線程上創建的,則發送到該窗口句柄的消息將由主線程的消息循環處理,並且一切正常。如果該窗口句柄是在後臺線程上創建的,則只能通過在該線程中運行的消息循環來檢索發送到該窗口句柄的消息。

試試這個:在後臺線程上進行COM調用之前,從主線程發出一個COM調用。這將強制COM在你的主線程上初始化你的消息循環。看看這是否會阻止後臺COM調用。只是一個想法。

+0

我試着在問題描述中創建線程之前在主線程上添加對StaticCall的調用。主線程上的StaticCall返回正常,但線程上的調用仍然阻塞。我會研究一下在後臺線程上抽取消息隊列的想法。謝謝 –

0

如果正常調用 - 沒有啓動新線程 - 該方法沒有任何問題,那麼主要是因爲您不會從創建COM對象的同一線程調用該方法。創建自定義調用機制,請參閱this

+0

「所有對COM的調用都被隔離在一個線程上。」通過這個我的意思是COM對象都創建並調用同一個線程。 –