紹介
ご存知のように、イベントハンドリングはメモリリークの一般的な原因です。もう使用されていないオブジェクトの永続化によって発生します。
この短い記事では、.Netフレームワークのコンテキスト・イベント・ハンドリングにおけるこの問題を示し、その後、この問題に対する標準的な解決策であるweak eventパターンをお教えします。これには2つの方法があります:
伝統的な "方法。
Net 4.5の フレームワークで、とてもシンプルです。
一般的なことから始めましょう
この記事の核心に真っ先に飛び込む前に、コードで最もよく使われる2つのもの、クラスとメソッドについて復習しておきましょう。
イベントソース
この点を説明するのに十分な複雑さを明らかにする****制限を持つ、基本的だが便利なイベント・ソース・クラスを紹介しましょう:
public class EventSource
{
public event EventHandlerEvent = delegate { };
public void Raise()
{
Event(this, EventArgs.Empty);
}
}
NULLデリゲートの奇妙な初期化メソッドについて興味がある人のために説明すると、これはイベントが常に初期化されていることを確認するためのトリックです。
ガベージコレクションをトリガーするための実践的アプローチ
.netでは、ゴミ収集は不確定な方法でトリガーされます。これは、決定論的な方法でオブジェクトの状態を追跡する必要がある実験には不都合です。
そのため、特定のメソッドですでにリリースされているパイプライン・コードをコピーしないようにしながら、定期的に独自のゴミ収集操作をトリガーすることが重要です:
static void TriggerGC()
{
Console.WriteLine("Starting GC.");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("GC finished.");
}
それほど複雑ではありませんが、このモデルにあまり馴染みのない方には少し説明が必要です:
*** GC.Collect()は、.netのCLRゴミ・コレクタをトリガします。これは、使用されなくなったオブジェクトや、クラス内にターミネータを持たないオブジェクトを掃除するには十分すぎる機能です。
GC.WaitForPendingFinalizers()は、他のオブジェクトのターミネーターが実行されるのを待ちます。
2番目のGC.Collect()は、新しく生成されたオブジェクトも確実にクリーンアップします。
問題提起
まず、イベント・リスナーの何が問題なのかを、いくつかの理論と最も重要なデモの助けを借りて理解してみましょう。
コンテキスト
オブジェクトをイベント・リスナーとして使用するには、そのインスタンス・メソッドの1つを、イベントを生成できる別のオブジェクトのイベント・ハンドラーとして登録する必要があります。また、イベントが発生したときにこのリスナーのハンドラー・メソッドを呼び出すために、イベント・ソースがイベント・リスナー・オブジェクトへの参照を保持する必要があります。
これは理にかなっていますが、参照が 強い参照である場合、リスナーはイベントソースへの依存として 機能するため、それを参照する***オブジェクトがイベントソースであっても、そのようにガベージコレクションすることはできません。
ここで何が起こっているのかを詳しく説明します:
インシデントハンドリングの問題
リスナーオブジェクトのライフサイクルをコントロールできるのであれば、これは問題ではありません。リスナーが不要になったら、イベントソースからサブスクライブを解除することができます。
しかし、リスナーのライフサイクル内で単一のレスポンスを検証できない場合、決定論的な方法で、あなたはそれを取り除くことはできません、あなたはそれを処理するためにGCに頼らなければなりません...これは、イベントソースがまだ存在する限り、あなたが準備したオブジェクトを考慮することはありません!
例
理屈はいいとして、問題と実際のコードを見てみましょう。
この勇気あるリスナーは、まだちょっと世間知らず。)
public class NaiveEventListener
{
private void OnEvent(object source, EventArgs args)
{
Console.WriteLine("EventListener received event.");
}
public NaiveEventListener(EventSource source)
{
source.Event += OnEvent;
}
~NaiveEventListener()
{
Console.WriteLine("NaiveEventListener finalized.");
}
}
簡単な例で、この仕組みを見てみましょう。
Console.WriteLine("=== Naive listener (bad) ===");
EventSource source = new EventSource();
NaiveEventListener listener = new NaiveEventListener(source);
source.Raise();
Console.WriteLine("Setting listener to null.");
listener = null;
TriggerGC();
source.Raise();
Console.WriteLine("Setting source to null.");
source = null;
TriggerGC();
出力。
EventListener received event.
Setting listener to null.
Starting GC.
GC finished.
EventListener received event.
Setting source to null.
Starting GC.
NaiveEventListener finalized.
GC finished.
このワークフローを分析してみましょう。
"": ローカルのイベントリスナーオブジェクトの参照にnullを代入します。
"": ガベージコレクションが開始されました。
"": ガベージ・コレクションが開始されましたが、 イベント・リスナーはリサイクラーによって回収されませんでした。
"":「source.Raise()」の 2 回目の呼び出しで、リスナーがまだ生きていることを確認します。
"": イベントの元のオブジェクトにはヌル値が割り当てられます。
"": 2回目のゴミ収集。
"": 今回、幼稚なイベントリスナーがようやく呼び戻されました。
"": 2回目のゴミ収集完了。
結論:イベント・ソースが再要求された場合に、イベント・リスナーが再要求されないようにするために、イベント・リスナーへの強力な参照が隠されています!
イベント・ソースが弱い参照を介してリスナーを参照できるようにし、イベント・ソースが存在するときにリスナー・オブジェクトを再要求することです。
#p#
弱イベントモード
この問題に.NET Frameworkでどのように対処できるかを見てみましょう。
通常は複数の方法がありますが、この場合は一筋縄ではいきません:
.Net4.5を使っているのであれば、シンプルな実装の
それに、人間のちょっとした技巧に頼らなければなりません。
伝統的アプローチ
WeakEventManager は、すべてのパターン・パイプラインのラッパーです。
WeakEventManager 、コンポーネントがWeakEventManagerフィッティングに接続するためのパイプラインです。
つまり、これは2段階のプロセスなのです。
まず、WeakEventManager: を継承してカスタムイベントマネージャを実装します。
StartListening メソッドと StopListening メソッドをそれぞれオーバーライドして、新しいハンドラの登録と既存のハンドラの登録を解除します。
AddListener "と "RemoveListener "という名前のリスナー・リストにアクセスする2つのメソッドを、カスタム・イベント・マネージャー・ユーザーのために提供します。
カスタム・イベント・マネージャの静的プロパティを公開して、現在のスレッドのイベント・マネージャを取得する方法を提供します。
次に、リスナーにIWeakEventListenrインターフェースを実装させます。
このイベントを処理してみてください
イベントが正しく処理された場合に true を返します。
言いたいことはたくさんありますが、比較的コードに変換できます。
まず最初に、カスタムの弱いイベントマネージャである
public class EventManager : WeakEventManager
{
private static EventManager CurrentManager
{
get
{
EventManager manager = (EventManager)GetCurrentManager(typeof(EventManager));
if (manager == null)
{
manager = new EventManager();
SetCurrentManager(typeof(EventManager), manager);
}
return manager;
}
}
public static void AddListener(EventSource source, IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
}
public static void RemoveListener(EventSource source, IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
}
protected override void StartListening(object source)
{
((EventSource)source).Event += DeliverEvent;
}
protected override void StopListening(object source)
{
((EventSource)source).Event -= DeliverEvent;
}
}
続いてイベントリスナーが続きます。
public class LegacyWeakEventListener : IWeakEventListener
{
private void OnEvent(object source, EventArgs args)
{
Console.WriteLine("LegacyWeakEventListener received event.");
}
public LegacyWeakEventListener(EventSource source)
{
EventManager.AddListener(source, this);
}
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
OnEvent(sender, e);
return true;
}
~LegacyWeakEventListener()
{
Console.WriteLine("LegacyWeakEventListener finalized.");
}
}
チェックします。
Console.WriteLine("=== Legacy weak listener (better) ===");
EventSource source = new EventSource();
LegacyWeakEventListener listener = new LegacyWeakEventListener(source);
source.Raise();
Console.WriteLine("Setting listener to null.");
listener = null;
TriggerGC();
source.Raise();
Console.WriteLine("Setting source to null.");
source = null;
TriggerGC();
出力。
LegacyWeakEventListener received event.
Setting listener to null.
Starting GC.
LegacyWeakEventListener finalized.
GC finished.
Setting source to null.
Starting GC.
GC finished.
イベントリスナーオブジェクトは、イベントソースオブジェクトがまだ生きていても、**** GCで適切にデストラクトできるようになりました。
しかし、単純なリスナーのためだけにたくさんのコードを書くのは、これらのリスナーがたくさんあり、それぞれのタイプに対して弱いイベントマネージャーを書かなければならないと想像してみてください!
コードのリファクタリングが得意なら、一般的なコードをリファクタリングする賢い方法を見つけることができます。
.Net4.5の 登場により、弱いイベントマネージャーは自分で実装する必要がありましたが、今では.Netがこの問題に対する標準的な解決策を提供していますので、今一度見直してみましょう!
ネット4.5アプローチ
Net 4.5 では、従来の WeakEventManager<TEventSource, TEventArgs> の新しいジェネリックバージョンが導入されました。
(このクラスはWindowsBaseコレクションにあります)。
コードもシンプルで読みやすい。
public class WeakEventListener
{
private void OnEvent(object source, EventArgs args)
{
Console.WriteLine("WeakEventListener received event.");
}
public WeakEventListener(EventSource source)
{
WeakEventManager.AddHandler(source, "Event", OnEvent);
}
~WeakEventListener()
{
Console.WriteLine("WeakEventListener finalized.");
}
}
シンプルなコードで、実にクリーン。
他の実装も同様に、イベント・リスナー・クラスにすべてをロードします。
Console.WriteLine("=== .Net 4.5 weak listener (best) ===");
EventSource source = new EventSource();
WeakEventListener listener = new WeakEventListener(source);
source.Raise();
Console.WriteLine("Setting listener to null.");
listener = null;
TriggerGC();
source.Raise();
Console.WriteLine("Setting source to null.");
source = null;
TriggerGC();
出力も確かに正しいです。
WeakEventListener received event.
Setting listener to null.
Starting GC.
WeakEventListener finalized.
GC finished.
Setting source to null.
Starting GC.
GC finished.
期待される結果は同じなのに、何が問題なの?
はんけつをくだす
このように、.Net上の弱いイベントモデルの 実装は、特に .Net 4.5では非常に簡単です。
Net 4.5では大量のコードが必要になるので、パターンにはこだわらず、C#を使い、メモリの問題があるかどうかを確認し、リークに気づいたら必要な時間をかけて実装することになるかもしれません。
しかし、.Net 4.5では、フレームワークによって管理され、自由でクリーンであるため、恐れることなく選択することができ、"+="や"-="のC#構文ほどクールではありませんが、セマンティクスは明確であり、それが重要です。