4

ファイナライザーは通常、管理されていないリソースを制御するために使用されることを私はよく知っています。ファイナライザーはどのような状況で管理対象のものを処理できますか?

私の理解では、ファイナライザキューに存在すると、オブジェクトまたはそれによって強く参照されるオブジェクトが収集されなくなりますが、(もちろん)ファイナライズからそれらを保護することはできません。通常のイベントでは、オブジェクトがファイナライズされると、そのオブジェクトはキューから削除され、参照するオブジェクトは次のGCパスで収集から保護されなくなります。ファイナライザーが呼び出されるまでに、オブジェクトによって参照されるオブジェクトの任意の組み合わせに対してファイナライザーが呼び出されている可能性があります。ファイナライザーが特定の順序で呼び出されることに依存することはできませんが、保持しているオブジェクト参照は引き続き有効である必要があります。

ファイナライザーがロックを取得したり、新しいオブジェクトを作成しようとしたりしてはならないことは明らかです。ただし、いくつかのイベントをサブスクライブするオブジェクトと、実際にイベントを使用する別のオブジェクトがあるとします。後者のオブジェクトがガベージコレクションの対象になった場合は、できるだけ早く前者のオブジェクトのイベントのサブスクライブを解除してもらいたいと思います。前者のオブジェクトは、ライブオブジェクトによってサブスクリプションが保持されなくなるまで、ファイナライズの対象になることはありません。

ロックフリーのリンクリストスタックまたはサブスクライブ解除する必要のあるオブジェクトのキューを用意し、メインオブジェクトのファイナライザーにスタック/キュー上の他のオブジェクトへの参照を配置させるのは実用的でしょうか?リンクリストアイテムオブジェクトは、メインオブジェクトの作成時に割り当てる必要があり(ファイナライザー内での割り当てが禁止されるため)、キューをポーリングするためにタイマーイベントなどを使用する必要があります(イベントのサブスクリプションが解除されるため)。ファイナライザースレッドの外部で実行する必要があり、ファイナライザーキューに何かが表示されるのを待つことだけを目的としたスレッドを作成するのはおそらくばかげていますが、ファイナライザーが事前に割り当てられたリンクリストオブジェクトを安全に参照できる場合そして、そのクラスに関連付けられたメインキューオブジェクト、

それは良い考えでしょうか?(注:私は.net 2.0を使用しています。また、スタックまたはキューに追加しようとすると、Threading.Interlocked.CompareExchangeで数回スピンする可能性がありますが、非常に長くスタックすることはないと思います)。

編集

確かに、イベントをサブスクライブするコードはiDisposableを実装する必要がありますが、使い捨てのものは常に適切に廃棄されるとは限りません。もしあれば、ファイナライザーは必要ありません。

私が懸念するシナリオは次のようなものです:iEnumerator(of T)を実装するクラスは、関連するクラスのchangeNotifyイベントにフックして、基になるクラスが変更された場合に列挙を適切に処理できるようにします(はい、Microsoftはすべての列挙型を考えています単にあきらめる必要がありますが、動作を継続できる列挙型の方が便利な場合もあります)。クラスのインスタンスは、数日または数週間の間に数千回または数百万回も列挙される可能性がありますが、その間はまったく更新されません。

理想的には、列挙子は破棄せずに忘れられることはありませんが、「foreach」と「using」が適用できないコンテキストで列挙子が使用されることがあります(たとえば、一部の列挙子はネストされた列挙をサポートします)。注意深く設計されたファイナライザーは、このシナリオに対処する手段を可能にするかもしれません。

ちなみに、更新を通じて継続することになっている列挙は、汎用のIEnumerable(of T)を使用する必要があります。iDisposableを処理しない非汎用フォームは、コレクションが変更された場合に例外をスローする必要があります。

4

4 に答える 4

3

ただし、いくつかのイベントをサブスクライブするオブジェクトと、実際にイベントを使用する別のオブジェクトがあるとします。後者のオブジェクトがガベージコレクションの対象になった場合は、できるだけ早く前者のオブジェクトのイベントのサブスクライブを解除してもらいたいと思います。前者のオブジェクトは、ライブオブジェクトによってサブスクリプションが保持されなくなるまで、ファイナライズの対象になることはありません。

「後のオブジェクト」がイベントを使用しているオブジェクトであり、「前の」オブジェクトがイベントをサブスクライブしているオブジェクトである場合、「前の」オブジェクトはイベント情報を「後の」オブジェクトに渡す何らかの方法を持っている必要があります-つまり、「後の」への参照が必要になります。おそらく、これにより「後の」オブジェクトがGC候補になるのを防ぐことができます。


そうは言っても、どうしても必要な場合を除いて、ファイナライザーを介したこのタイプの管理対象リソースの割り当て解除は避けることをお勧めします。あなたが説明しているアーキテクチャは非常に壊れやすく、正しく理解するのは非常に難しいようです。これはおそらくIDisposableのより良い候補であり、ファイナライザーは「最後の溝」のクリーンアップ作業です。

IDisposableは通常、ネイティブリソースのリリースに関するものですが、サブスクリプション情報を含む任意のリソースのリリースに関するものである可能性があります。

また、オブジェクト参照の単一のグローバルコレクションを持たないようにします。オブジェクトを内部的にWeakReferenceを使用する方が理にかなっている場合があります。「後の」オブジェクトが収集されるとすぐに、「前の」オブジェクトのWeakReferenceは無効になります。次回イベントサブスクリプションが発生したときに、内部WeakReferenceが無効になった場合は、自分でサブスクライブを解除できます。グローバルキューやリストなどは必要ありません-動作するはずです...

于 2010-07-26T17:38:30.343 に答える
0

私が理解していることを確認させてください-収集されたイベントパブリッシャーにサブスクライブされたままのイベントサブスクライバーからのリークについて心配していますか?

そうだとすれば、心配する必要はないと思います。

「前の」オブジェクトがイベントサブスクライバーであり、「後の」オブジェクトがイベントパブリッシャー(イベントを発生させる)であると仮定すると、次のようになります。

サブスクライバー(前者)が「サブスクライブ」されている唯一の理由は、デリゲートオブジェクトを作成し、そのデリゲートをパブリッシャー(「後期」)に渡したためです。

デリゲートメンバーを見ると、サブスクライバーオブジェクトと実行されるサブスクライバーのメソッドへの参照があります。したがって、次のような参照チェーンがあります:パブリッシャー->デリゲート->サブスクライバー(パブリッシャーは、サブスクライバーを参照するデリゲートを参照します)。これは一方向のチェーンです。サブスクライバーはデリゲートへの参照を保持していません。

したがって、デリゲートを維持する唯一のルートはパブリッシャー(「後者」)にあります。後者がGCの対象になると、代理人も対象になります。サブスクライバーがサブスクライブを解除するときに実行する特別なアクションがない限り、デリゲートが収集されると、サブスクライバーは事実上サブスクライブ解除されます-リークはありません)。

編集

スーパーキャットのコメントによると、問題はパブリッシャーがサブスクライバーを存続させていることだと思われます。

それが問題である場合、ファイナライザーは役に立ちません。理由:パブリッシャーは(デリゲートを介して)サブスクライバーへの実際の誠実な参照を持っており、パブリッシャーはルート化されているため(それ以外の場合はGCの対象となります)、サブスクライバーはルート化され、ファイナライズまたはGCの対象にはなりません。

パブリッシャーがサブスクライバーを存続させるのに問題がある場合は、weak-refイベントを検索することをお勧めします。開始するためのリンクは次のとおりです。http://www.codeproject.com/KB/cs/WeakEvents.aspxhttp : //www.codeproject.com/KB/architecture/observable_property_patte.aspx

私も一度これに対処しなければなりませんでした。効果的なパターンのほとんどは、デリゲートへの弱い参照を保持するようにパブリッシャーを変更することを含みます。次に、新しい問題が発生します。デリゲートがルート化されていないため、なんとかしてそれを存続させることができます。上記の記事はおそらくそのようなことをします。一部の手法では反射を使用します。

私はかつて、反射に依存しない手法を使用しました。ただし、パブリッシャーとサブスクライバーの両方でコードを変更できる必要がありました。そのソリューションのサンプルをご覧になりたい場合は、お知らせください。

于 2010-07-26T18:52:11.170 に答える
0

オブジェクトを「パブリッシャー」および「サブスクライバー」と呼び、問題の理解を言い換えます。

C#では、パブリッシャーはサブスクライバーへの参照を(効果的に)保持し、サブスクライバーがガベージコレクションされるのを防ぎます。サブスクリプションを明示的に管理せずにサブスクライバーオブジェクトをガベージコレクションできるようにするにはどうすればよいですか?

まず、そもそもこの状況を回避するためにできる限りのことをすることをお勧めします。とにかく質問を投稿していることを考慮して、次に進み、あなたが持っていると仮定します=)

次に、パブリッシャーのイベントの追加および削除アクセサーをフックし、WeakReferencesのコレクションを使用することをお勧めします。その後、イベントが呼び出されるたびに、これらのサブスクリプションのフックを自動的に解除できます。これは非常に大まかな、テストされていない例です。

private List<WeakReference> _eventRefs = new List<WeakReference>();

public event EventHandler SomeEvent
{
    add
    {
        _eventRefs.Add(new WeakReference(value));
    }
    remove
    {
        for (int i = 0; i < _eventRefs; i++)
        {
            var wRef = _eventRefs[i];
            if (!wRef.IsAlive)
            {
                _eventRefs.RemoveAt(i);
                i--;
                continue;
            }

            var handler = wRef.Target as EventHandler;
            if (object.ReferenceEquals(handler, value))
            {
                _eventRefs.RemoveAt(i);
                i--;
                continue;
            }
        }
    }
}
于 2010-07-26T18:58:11.390 に答える
0

これをもう一度試してみましょう。次のように、イベントハンドラーをパブリッシャーに追加できますか?

var pub = new Publisher();
var sub = new Subscriber();
var ref = new WeakReference(sub);

EventHandler handler = null; // gotta do this for self-referencing anonymous delegate

handler = (o,e) =>
{
    if(!ref.IsAlive)
    {
        pub.SomeEvent -= handler; // note the self-reference here, see comment above
        return;
    }


    ((Subscriber)ref.Target).DoHandleEvent();
};

pub.SomeEvent += handler;

このように、デリゲートはサブスクライバーへの直接参照を保持せず、サブスクライバーが収集されるたびに自動的にフックを解除します。これをSubscriberクラスのプライベート静的メンバーとして(カプセル化の目的で)実装できます。「this」オブジェクトへの直接参照を誤って保持しないように、静的であることを確認してください。

于 2010-07-27T15:05:42.080 に答える