この疑いの確認を検索する方法を見つけることができませんでしたが、あるスレッドで作成された COM オブジェクトが他のスレッドで使用できなくなっているという証拠が見られます (基になる RCW から分離された COM オブジェクトは使用できません)。 ) コードを作成したスレッドでコードの実行が停止すると (そのスレッドは終了している可能性があります)。コード全体に呼び出しがあるため、これは追跡するのが非常に厄介な問題ですがSystem.Runtime.InteropServices.Marshal.ReleaseComObject
、それらのいずれかが呼び出されてこのエラーが発生したことを特定できませんでした。最後に、セカンダリ スレッドの実行が停止したときに、COM オブジェクトが暗黙的に解放されているように見えるという結論に達しました。これは本当でしょうか?これは文書化された動作ですか?
2 に答える
はい、COM オブジェクトは強いスレッド アフィニティを持つ傾向があります。スレッド化は、COM における小さな実装の詳細ではありません。.NET とは異なり、COM は COM クラスに対してスレッド セーフを保証します。COM は、サポートするスレッドの種類を公開できます。「アパートメント」(つまり、「スレッドセーフではない」) は非常に一般的な選択肢です。COM は、プログラムが何もしなくても、これらの要件が確実に満たされるようにします。オブジェクトが常にスレッドセーフな方法で使用されるように、あるスレッドから別のスレッドへの呼び出しをマーシャリングすることは自動的に行われます。.NET コードでは、通常、たとえば Control.BeginInvoke や Dispatcher.BeginInvoke を使用して、これを自分で行う必要があります。
この自動的な結果として、終了が許可されている 1 つ以上の COM オブジェクトを所有するスレッドは、これらのオブジェクトを自動的に解放します。スレッドの安全性要件を満たす方法がなくなったため、これが必要になります。とにかくこれを使用しようとすると、爆撃されます。これらのオブジェクトにサービスを提供し続けるために、スレッドが十分長く存続することを保証する以外に、これを解決する方法はありません。同様に、Dispatcher.BeginInvoke が .NET で引き続き動作できるように、UI スレッドを十分長く維持する必要があります。
Fwiw、はい、Marshal.ReleaseComObject() を使用すると、これについて多くの不満を抱くことができます。明示的なメモリ管理には、バグのあるプログラムを作成してきた長い歴史があり、自動ガベージ コレクションによって問題が解決されました。GC は、ユーザーの助けなしにその COM オブジェクトを解放する能力があり、間違いを犯すことはありません。それに慣れるにはもう少し時間がかかります。COM オブジェクトのリソース使用量が異常に高く、確定的に解放する必要があることがわかっている場合は、高価な .NET オブジェクト グラフに対して行うのとまったく同じことを行います。GC.Collect() はそれを支援します。Marshal.ReleaseComObject() が不必要に使用される傾向がある理由については、この回答を確認してください。
Hans Passantの回答の動作を再現することができたサンプルコードを次に示します。ボタン 1 をクリックしてオブジェクトを作成し、作成スレッドの終了後にボタン 2 をクリックしてアクセスすると、「基になる RCW から分離された COM オブジェクトは使用できません」というエラーが表示されます。
Public Class Form1
Dim comRef As Microsoft.Office.Interop.Outlook.Application
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New System.Threading.Thread(AddressOf CreateApplication)
t.SetApartmentState(Threading.ApartmentState.STA)
t.Start()
End Sub
Private Sub CreateApplication()
comRef = New Microsoft.Office.Interop.Outlook.Application
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
TextBox1.Text = comRef.DefaultProfileName
End Sub
End Class