2013-03-29 7 views
5

私が作成したライブラリには、.NET SerialPortクラスに似た機能を実装するDataPortクラスがあります。いくつかのハードウェアと会話し、そのハードウェアを介してデータが入力されるたびにイベントを発生させます。この動作を実装するために、DataPortは、DataPortオブジェクトと同じ寿命を持つと予想されるスレッドをスピンアップします。 問題は、IDisposableインターを実装し、データポートがスコープの外に出たとき、それはハードウェアへのデータポートの交渉が(のPInvokeを使用して)いるので、ゴミが今スレッドのガベージコレクションを防止するスレッド

を収集し得ないといくつかのアンマネージリソースを所有している決してということです。オブジェクトに対してDisposeを呼び出すと、すべてが正しく行われます。 DataPortは管理されていないすべてのリソースを取り除き、ワーカースレッドを殺して遠ざかります。しかし、DataPortをスコープから外してしまえば、ガベージコレクタは決してファイナライザを呼び出さず、DataPortは永遠にメモリに残っています。先に進む前に、:私は、これは二つの理由のために何が起こっている知っている:

  1. ファイナライザにブレークポイント
  2. 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ました。この例のすべてで管理されたリソースが使用されても、私は同じ問題が表示されます。
+0

'SafeHandle'または' CriticalHandle'から派生したクラスを使用して、アンマネージドリソースをラップする必要があります。あなたのライブラリのいずれかのクラスにfinalizerがあり、それらのうちのどれかを継承しない場合、おそらく大きなバグが発生するのを待っているかもしれません。もちろん例外はありますが、かなり珍しいので、私はしばらくの間に直面していません。ここに[出発点](http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid = 88e62cdf-5919-4ac7-bc33-20c06ae539ae)これを理解するために、管理されていないクリーンアップに関する追加の参考資料が必要な場合は、私に連絡してください。 –

+0

ここからメモリにアクセスしますが、スレッドは暗黙的なgcルートを作成しませんか? (おそらくisbackgroundとして設定されていない限り) – JerKimball

+0

@ 280Z28この問題のP/Invoke/unmanaged部分はおそらく関連しませんが、例の最初の部分では漏れています。関連する管理されていない唯一のリソースは、すでにSafeHandleとして実装されているOpen()メソッドでDLLが返すハードウェアへのハンドルです。 dataReady ManualResetEventはアンマネージドワールドに渡されますが、P/Invokeマーシャラがそれを処理します。この問題は、管理されていないリソースがなければ発生します。 DataPortはガーベッジ・コレクションを取得せず、所有するスレッドは永遠に存続します。 –

答えて

4

暗黙の「This」パラメータを削除するには、ワーカースレッドメソッドを少し変更して、パラメータとして参照:衝撃的

public static void WorkerThreadMethod(object thisParameter) 
{ 
    //Extract the things we need from the parameter passed in (the DataPort) 
    //dataReady used to be 'this.dataReady' and closeSignal used to be 
    //'this.closeSignal' 
    ManualResetEvent dataReady = ((DataPort)thisParameter).dataReady; 
    WaitHandle closeSignal = ((DataPort)thisParameter).closeSignal; 

    thisParameter = null; //Forget the reference to the DataPort 

    for(;;) 
    { 
    //Same as before, but without "this" . . . 
    } 
} 

これで問題が解決しませんでした!

SOS.dllに戻って、私はThreadHelperオブジェクトによって保持されているDataPortへの参照がまだあることを知りました。明らかに、Thread.Start(this);を実行してワーカースレッドを起動すると、Startメソッドに渡した参照(I'm inferring)に保持されているスレッドと同じ存続期間を持つプライベートThreadHelperオブジェクトが作成されます。それは私たちに同じ問題を残す。何かがDataPortへの参照を保持しています。今、我々はOKだ

//Code that starts the thread: 
    Thread.Start(new WeakReference(this)) 
//. . . 
public static void WorkerThreadMethod(object weakThisReference) 
{ 
    DataPort strongThisReference= (DataPort)((WeakReference)weakThisReference).Target; 

    //Extract the things we need from the parameter passed in (the DataPort) 
    ManualResetEvent dataReady = strongThisReferencedataReady; 
    WaitHandle closeSignal = strongThisReference.closeSignal; 

    strongThisReference= null; //Forget the reference to the DataPort. 

    for(;;) 
    { 
    //Same as before, but without "this" . . . 
    } 
} 

:さんはこの1つ以上試してみてみましょう。作成されるThreadHelperはWeakReferenceに保持されますが、ガーベジコレクションには影響しません。私たちはワーカースレッドの始めにDataPortから必要なデータだけを抽出し、DataPortへのすべての参照を意図的に失います。このアプリケーションでは、DataPortのライフタイムにわたって変化しない部分があるため、これは問題ありません。現在、トップレベルアプリケーションがDataPortへのすべての参照を失うと、ガベージコレクションの対象となります。 GCは、ワーカースレッドを終了させるDisposeメソッドを呼び出すファイナライザを実行します。すべてが幸せです。

しかし、これは本当に苦痛です(または少なくとも右になります)!そのオブジェクトと同じ生存期間を持つスレッドを所有するオブジェクトを作る良い方法はありますか?あるいは、スレッドなしでこれを行う方法はありますか?

エピローグ: 代わりWaitHandle.WaitAnyを(やってそのほとんどの時間を費やしているスレッド)を有するので、あなたはそれ自身のスレッドを必要としなかった待機ハンドルのいくつかの並べ替えを持っていることができればそれは素晴らしいことだが、スレッドプールスレッドが実行されると、スレッドプール上で継続を開始します。同様に、ハードウェアDLLは、イベントを通知する代わりに新しいデータがあるたびに代理人を呼び出すことができますが、そのDLLを制御しません。

+0

'Thread'オブジェクトではなく' Tasks'を使用する理由は何ですか? –

+0

この質問は、Tasksより前の.NET Frameworkのバージョンに基づいていますが、Tasksが問題を解決しているかどうかはわかりません。私は、イベントループなしでハードウェア割り込みを待つ方法はないと思います。私は管理されていないハードウェアdllを制御しないので、イベントがあるときに代理人に呼び出す「プッシュ」モデルにすることはできません。 –

+0

ああ、それは理にかなっています。私はタスクがあなたの問題を解決するとは思わない。それらは面倒さが少なく、場合によってはパフォーマンスが向上します。フレームワークは、ユーザー要求で_n_数のスレッドを作成するのではなく、スレッドプールからスピンオフするスレッドの数を決定するためです。あなたが言ったように、タスクに変更すると私は問題を解決するとは思わない。ちょうどの観測:) –

0

私はこの問題があなたが示したコードではなく、このシリアルポートラッパークラスを使用しているコードにあると信じています。 「使用中」ステートメントがない場合は、http://msdn.microsoft.com/en-us/library/yh598w02.aspxを参照してください。確定的なクリーンアップの動作がありません。代わりに、ガベージコレクタに依拠しますが、まだ参照されているオブジェクトを得ることはありません。また、スレッドのすべてのスタック変数(通常のパラメータまたはthis-pointerとして)は参照としてカウントされます。

+0

Disposeメソッドが実行され、ワー​​カースレッドを強制終了するため、usingステートメントまたはDataPortオブジェクトのdisposeを呼び出すと問題が修正されます。しかし、私は確信していますが、Disposeに電話することを忘れても、ガベージコレクタは最終的にそれを取得する必要があります。 –

+0

Hmm。私はGCがそれを解決できるとは思わない。 http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reachability_of_an_objectを見ると、スタックまたはグローバルのすべてのものが到達可能であるとみなされます。これは任意のスレッドのスタックに適用されるため、DataPortへの参照があれば、スレッドは存続します。使用されたハンドルとリセットイベントへの参照のみを渡すことができます。これにより、残りのハンドルと切り離されます。 –

関連する問題