私が作成したライブラリには、.NET SerialPort クラスと同様の機能を実装するクラス DataPort があります。一部のハードウェアと通信し、データがそのハードウェアを介して着信するたびにイベントを発生させます。この動作を実装するために、DataPort は、DataPort オブジェクトと同じ有効期間を持つと予想されるスレッドをスピンアップします。 問題は、DataPort が範囲外になると、ガベージ コレクションが行われないことです。
現在、DataPort は (pInvoke を使用して) ハードウェアと通信し、管理されていないリソースを所有しているため、IDisposable を実装しています。オブジェクトで Dispose を呼び出すと、すべてが正しく行われます。DataPort は、管理されていないリソースをすべて取り除き、ワーカー スレッドを強制終了して消えます。ただし、DataPort をスコープ外に出すだけでは、ガベージ コレクターがファイナライザーを呼び出すことはなく、DataPort はメモリ内で永久に存続します。私はこれが2つの理由で起こっていることを知っています:
- ファイナライザーのブレークポイントはヒットしません
- SOS.dllは、DataPort がまだ生きていることを教えてくれます
サイドバー:先に進む前に、答えは「Call Dispose() Dummy!」であることはわかっています。しかし、すべての参照をスコープ外に出しても、最終的には正しいことが起こり、ガベージ コレクターは DataPort を取り除く必要があると思います
問題に戻る: SOS.dll を使用すると、DataPort がガベージ コレクションされない理由は、それがスピンアップしたスレッドがまだ DataPort オブジェクトへの参照を持っているためであることがわかります。スレッドが実行しているインスタンス メソッド。実行中のワーカー スレッドはガベージ コレクションの対象にならないため、実行中のワーカー スレッドのスコープ内にある参照もガベージ コレクションの対象になりません。
スレッド自体は、基本的に次のコードを実行します。
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()
}
}
}
Dispose メソッドには、次の (関連する) コードが含まれています。
public void Dispose()
{
closeSignal.Set();
workerThread.Join();
}
スレッドは gc ルートであり、DataPort への参照を保持しているため、DataPort はガベージ コレクションの対象にはなりません。ファイナライザーが呼び出されることはないため、クローズ シグナルをワーカー スレッドに送信することはありません。ワーカー スレッドはクローズ シグナルを受信しないため、その参照を永久に保持し続けます。アック!
この問題に対して私が考えることができる唯一の答えは、WorkerThread メソッドの「this」パラメーターを取り除くことです (以下の回答で詳しく説明します)。他の誰かが別のオプションを考えることができますか? オブジェクトの寿命が同じスレッドでオブジェクトを作成するより良い方法があるはずです! あるいは、別のスレッドなしでこれを行うことはできますか? 通常の .NET シリアル ポート クラスの内部実装の詳細について説明している msdn フォーラムのこの投稿に基づいて、この特定の設計を選択しました。
コメントから少し追加情報を更新します。
- 問題のスレッドでは IsBackground が true に設定されています
- 上記の管理されていないリソースは、問題には影響しません。例のすべてがマネージド リソースを使用していたとしても、同じ問題が発生します。