私が作成したライブラリには、.NET SerialPortクラスに似た機能を実装するDataPortクラスがあります。いくつかのハードウェアと会話し、そのハードウェアを介してデータが入力されるたびにイベントを発生させます。この動作を実装するために、DataPortは、DataPortオブジェクトと同じ寿命を持つと予想されるスレッドをスピンアップします。 問題は、IDisposableインターを実装し、データポートがスコープの外に出たとき、それはハードウェアへのデータポートの交渉が(のPInvokeを使用して)いるので、ゴミが今スレッドのガベージコレクションを防止するスレッド
を収集し得ないといくつかのアンマネージリソースを所有している決してということです。オブジェクトに対してDisposeを呼び出すと、すべてが正しく行われます。 DataPortは管理されていないすべてのリソースを取り除き、ワーカースレッドを殺して遠ざかります。しかし、DataPortをスコープから外してしまえば、ガベージコレクタは決してファイナライザを呼び出さず、DataPortは永遠にメモリに残っています。先に進む前に、:私は、これは二つの理由のために何が起こっている知っている:
- ファイナライザにブレークポイント
- SOS.dllを打つことは決してありませんしてデータポートがまだ生き
サイドバーであることを私に伝えます私はそう答えていると思います。「ダミーを呼出してください!」私はあなたがすべての参照がスコープ外に行かせていても、正しいことは、最終的にとガベージコレクタはバックナンバーにデータポート
を取り払うべきで起こるべきだと思う:SOS.dllを使用して、私は私のDataPortがガーベジコレクションされていない理由は、スレッドが実行しているインスタンスメソッドの暗黙的な "this"パラメータを通して、それがスピンアップしたスレッドにDataPortオブジェクトへの参照が残っているためです。稼働中のワーカースレッドwill not be garbage collected。実行中のワーカースレッドのスコープ内にある参照もガベージコレクションに適格ではありません。
スレッド自体は基本的に次のコードを実行:
public void WorkerThreadMethod(object unused)
{
ManualResetEvent dataReady = pInvoke_SubcribeToEvent(this.nativeHardwareHandle);
for(;;)
{
//Wait here until we have data, or we got a signal to terminate the thread because we're being disposed
int signalIndex = WaitHandle.WaitAny(new WaitHandle[] {this.dataReady, this.closeSignal});
if(signalIndex == 1) //closeSignal is at index 1
{
//We got the close signal. We're being disposed!
return; //This will stop the thread
}
else
{
//Must've been the dataReady signal from the hardware and not the close signal.
this.ProcessDataFromHardware();
dataReady.Reset()
}
}
}
廃棄方法は、以下の(関連)コード含ま:スレッドはGCルートであり、それは参照を保持するため
public void Dispose()
{
closeSignal.Set();
workerThread.Join();
}
をDataPortには、DataPortはガベージコレクションの対象にはなりません。ファイナライザは決して呼び出されないので、クローズ信号をワーカースレッドに送信することはありません。ワーカースレッドは決してクローズシグナルを取得しないので、永遠にその参照を保持し続けます。 ACK!
私がこの問題を考えることができる唯一の答えは、WorkerThreadメソッド(以下の解答で詳しく説明します)で 'this'パラメータを取り除くことです。他の誰かが別のオプションを考えることはできますか?オブジェクトの寿命と同じスレッドを持つオブジェクトを作成するには、より良い方法が必要です。これとは別のスレッドを使わずにこれを行うこともできますか?私はこの特定のデザインを、msdnフォーラムでthis postに基づいて選択しました。このフォーラムでは、正規の内部実装の詳細を説明しています。NETのシリアルポートクラス
更新コメントからの追加情報のビット:
- 問題のスレッドは、上記のアンマネージリソースが問題に影響を与えません真
- に設定IsBackgroundました。この例のすべてで管理されたリソースが使用されても、私は同じ問題が表示されます。
'SafeHandle'または' CriticalHandle'から派生したクラスを使用して、アンマネージドリソースをラップする必要があります。あなたのライブラリのいずれかのクラスにfinalizerがあり、それらのうちのどれかを継承しない場合、おそらく大きなバグが発生するのを待っているかもしれません。もちろん例外はありますが、かなり珍しいので、私はしばらくの間に直面していません。ここに[出発点](http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid = 88e62cdf-5919-4ac7-bc33-20c06ae539ae)これを理解するために、管理されていないクリーンアップに関する追加の参考資料が必要な場合は、私に連絡してください。 –
ここからメモリにアクセスしますが、スレッドは暗黙的なgcルートを作成しませんか? (おそらくisbackgroundとして設定されていない限り) – JerKimball
@ 280Z28この問題のP/Invoke/unmanaged部分はおそらく関連しませんが、例の最初の部分では漏れています。関連する管理されていない唯一のリソースは、すでにSafeHandleとして実装されているOpen()メソッドでDLLが返すハードウェアへのハンドルです。 dataReady ManualResetEventはアンマネージドワールドに渡されますが、P/Invokeマーシャラがそれを処理します。この問題は、管理されていないリソースがなければ発生します。 DataPortはガーベッジ・コレクションを取得せず、所有するスレッドは永遠に存続します。 –