1

プライベートメンバーとしてConcurrentDictionaryを持つクラスがあります。このクラスは、デリゲート/コールバックメソッドも定義します。基本クラスは、このメソッドを外部イベントのコールバックとして登録します。これは一度だけです。

ANTメモリプロファイラーを実行していますが、ConcurrentDictionaryプロパティの数百のインスタンスから参照されるMyObjの数千のインスタンスが表示されています。これらのGCルートはイベントコールバックです。

これにより、アプリケーションの実行中にメモリが大幅に増加しているようです。おそらく約5分後、そのメモリのかなりの部分が再利用されますが、アプリはすぐに膨らむため、問題が発生する可能性があるのではないかと心配しています。そしてGCが始まる前の長い間。

ここで何が起こっているのですか、どうすれば解決できますか?

これは、ハンドラーを登録する基本呼び出しのスニペットです

protected abstract void DataReceivedEventHandler(DataChangedEvent evt);

public virtual void RegisterForChanges(ICollection<MemoryTable> tables)
{
    foreach (MemoryTable table in tables)
    {
        _subscribedTables.Add(table);
        table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));

    }
}

上記の基本クラスのサブクラスに実装されているハンドラーは次のとおりです。

private ConcurrentDictionary<string, DataRecord> _cachedRecords;

protected override void DataReceivedEventHandler(DataChangedEvent evt)
{
    DataRecord record = evt.Record as DataRecord;
    string key = record.Key;
    if (string.IsNullOrEmpty(key)) { return; }

    if (_cachedRecords.ContainsKey(key))
    {
        _cachedRecords[key] = record;

        DateTime updateTime = record.UpdateTime;
        TimeSpan delta = updateTime - _lastNotifyTime;
        if (delta.TotalMilliseconds > _notificationFrequency)
        {
            PublishData(updateTime);
        }
    }
}

publishDataメソッドはプリズムイベントを公開します

4

5 に答える 5

5

どうしたの

はい、イベントはデリゲートのリストであり、2つの関連フィールドがあります:targetmethod。静的メソッドを参照している場合を除き、targetはクラスへの参照です。そして、どのメソッドを呼び出すかをイベントに伝えるmethodリフレクションです。MemberInfo

トラブルシューティング方法

メソッドにブレークポイントを設定することを検討してくださいadd_EventName。(明示的なadd_EventNameとremove_EventNameがない場合は、この明示的なコードを使用してイベントを再定義する必要があります)。

  private event EventHandler eventName;
  public event EventHandler EventName
  {
     add { eventName += value; } // Breakpoint here
     remove { eventName -= value; }
  }

これは、なぜそれが何度も購読されているのかを見つけるのに役立ちます。

于 2012-06-01T15:44:26.877 に答える
1

外部イベントが1回発生した後、クラスのサブスクライブを解除します(SomeClass.SomeEvent-= MyEventHandler)。または、WeakReferencesを使用して確認できます。

于 2012-06-01T15:43:31.237 に答える
1

デリゲートには、オブジェクトへの強力な参照と、そのオブジェクトで呼び出すメソッドの指示が含まれています。したがって、デリゲートへの強い参照を保持するライブオブジェクトは、デリゲートが操作するオブジェクト(およびそのオブジェクトが強い参照を保持するオブジェクト)を存続させます。

他のオブジェクトの利益のためにオブジェクトを操作するコールバックまたはイベントを登録したい場合がありますが、コールバックまたはイベントは、それ自体のためだけにオブジェクトを存続させるべきではありません。たとえば、オブジェクトは、存続期間の長いオブジェクトが特定のイベントを発生させた回数をカウントし、イベントにアタッチする「イベントカウンター」オブジェクトのインスタンスを作成したい場合があります。存続期間の長いオブジェクトがカウンターオブジェクトのイベントサブスクリプションを保持している限り、そのカウンターオブジェクトは存続し、イベントが発生するたびにカウンターがインクリメントされます。もちろん、これまでカウンターを見たことがある人がいなくなった場合、カウンターも、カウンターを増やすために必要な労力も、有用な目的には役立ちません。

将来のある特定の時点で発生すると予想されるコールバックがあるが、そのコールバックは、それが動作するオブジェクトへのライブ参照(コールバック自体の外部)が存在する場合にのみ有用な目的を果たします。コールバックを転送オブジェクトに登録しておくと便利です。転送オブジェクトは、意味がある場合は、呼び出しをメインオブジェクトに転送します。これを実現する最も簡単な方法は、転送オブジェクトにを保持させることWeakReferenceです。Target転送オブジェクトは、呼び出しを受信すると、からを取得しますWeakReference。null以外の場合は、取得したターゲットをメインオブジェクトの型にキャストし、適切なメソッドを呼び出します。コールバックが実行される前にメインオブジェクトが存在しなくなった場合、WeakReference.Targetプロパティはnullになり、転送オブジェクトのコールバックは単にサイレントに戻ります。

Targetさらにいくつかの注意事項:(1)をデリゲートに設定して呼び出すことは魅力的かもしれませんがWeakReference、そのアプローチは、実際のターゲットオブジェクト自体がそのデリゲートへの参照を保持している場合にのみ機能します。そうしないと、ターゲットがそうでない場合でも、デリゲート自体がガベージコレクションの対象になります。(2)をインターフェースにキャストしWeakReference、メインオブジェクトにそのインターフェースを実装させると役立つ場合があります。これにより、1つの転送オブジェクトクラスを他の多くのクラスで使用できるようになります。1つのクラスを多数の弱いイベントにアタッチする必要がある場合は、汎用インターフェイスを使用すると便利な場合があります。

インターフェイスIDispatchAction<DummyType、ParamType>
{{
  void Act(ParamType ref param);
}

これにより、メインオブジェクトがいくつかのIDispatchActionアクションを公開できるようになります(たとえば、クラスがandを実装IDispatchAction<foo,int>.Actしている場合、IDispatchAction<bar,int>.Actそのクラスへの参照をそれらのインターフェイスの1つにキャストし、Actそれを呼び出すと適切なメソッドが呼び出されます)。

于 2012-06-01T16:05:05.650 に答える
0

テーブルを何度も再サブスクライブしている可能性はありますか?私はこれを見る:

foreach (MemoryTable table in tables)
{
    _subscribedTables.Add(table);
    table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));
}

そして、テーブルが再サブスクライブされていないことを確認するためのチェックが表示されることを期待しています。

foreach (MemoryTable table in tables)
{
    if (!_subscribedTables.Contains(table)) {
        _subscribedTables.Add(table);
        table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));
    }
}

編集:質問の冒頭のコメントを考えると、私は問題(あなたがそれを問題と呼ぶことができるなら)がここにあるとかなり確信しています:

if (_cachedRecords.ContainsKey(key))
{
    _cachedRecords[key] = record;

ここで言っているのは、レコードのキーがすでにに存在する場合はcachedRecords、値を(おそらく)新しい行インスタンスに置き換えるということです。これはおそらく、バックグラウンドプロセスによって行のデータが変更され、それらの新しい値をUIに伝達する必要があるためです。

私の推測では、MemoryTableクラスは、これらの変更のためにDataRecordの新しいインスタンスを作成し、その新しいインスタンスをイベントチェーンの上位にあるハンドラーに送信しています。イベントが何千回も発生した場合、もちろん、メモリ内に何千回も発生することになります。ガベージコレクターは通常、これらのものをクリーンアップするのに非常に優れていますが、これらのインスタンスが収集されるときに発生する大規模なGCを回避するために、インプレース更新を検討することをお勧めします。

すべきでないことは、GCがいつ実行されるかを制御(または予測)しようとすることです。GCが収集した後、余分なオブジェクトがなくなっていることを確認してください(つまり、それらがリークされていないことを確認してください)。

于 2012-06-01T16:52:06.850 に答える
0

オブジェクト定義のコールバックメソッドをメモリに残したくない場合は、このメソッドを静的として定義する必要があります。

于 2016-03-16T06:45:26.163 に答える