「弱いイベント」(Weak Event)パターンで
WeakEventManagerを継承したクラスが以下のようなエラーを投げて
びっくりしたのでメモ。
ランタイムの重大なエラーが発生しました。エラーのアドレスは 0xXXXXXXXX、スレッド 0xXXXX です。エラー コードは 0x80131623 です。これは CLR のバグであるか、またはユーザー コードのアンセーフまたは確認不可能な部分にバグがある可能性があります。このバグの一般的な原因には、スタックが壊れる可能性のある COM-interop または PInvoke のユーザー マーシャリング エラーが含まれています。
※この記事では.NET 4.0のWeak Eventパターン実装について書いてます。
.NET 4.5ではWeak Event周りが強化されていて
書き方もちょっと変わってるはずなので、参考にならないかも。
Weak Eventパターンって何よって人には、詳しくはこのへんを見ていただければですが、
簡単にいうと、イベント購読によるメモリリークを防ぐための書き方です。
リスナオブジェクトへの参照がなくなったときに
イベント購読が自動的に破棄される便利なAPIを使います。
はじめに、(.NET 4.0までの)シンプルな実装例を。
まず、Hogeイベントを持っているクラス。
HogeEventArgsは適当なEventArgsのサブクラスです。
class HogeEventSource { public event EventHandler<HogeEventArgs> Hoge; //Hogeイベントを発生させる public void RaiseHoge() { var h = Hoge; if (h != null) h(this, new HogeEventArgs()); } }
そして、このHogeイベントを弱参照で利用するために
WeakEventManagerを継承したクラスを作る。
class HogeEventManager : WeakEventManager { //現在のインスタンスを取得するプロパティ private static HogeEventManager CurrentManager { get { var mng = GetCurrentManager(typeof(HogeEventManager)) as HogeEventManager; if (mng == null) { mng = new HogeEventManager(); SetCurrentManager(typeof(HogeEventManager), mng); } return mng; } } protected override void StartListening(object source) { (source as HogeEventSource).Hoge += OnHoge; } protected override void StopListening(object source) { (source as HogeEventSource).Hoge -= OnHoge; } private void OnHoge(object sender, HogeEventArgs e) { DeliverEvent(sender, e); } //ハンドラを登録するための公開メソッド public static void AddListener(HogeEventSource source, HogeEventListener listener) { CurrentManager.ProtectedAddListener(source, listener); } }
このHogeイベントを購読するクラスではIWeakEventListenerインタフェースを実装する。
class HogeEventListener : IWeakEventListener { //Hogeイベントのハンドラを登録する public void RegisterHogeHandler(HogeEventSource eventSource) { HogeEventManager.AddListener(eventSource, this); } //全てのWeakEventを受け取るハンドラ public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { //WeakEventManagerの型でふるい分けする if (managerType == typeof(HogeEventManager)) { OnHoge(sender, (HogeEventArgs)e); return true; } //知らない型のときはfalseを返す return false; } //Hogeイベント受信時の処理 private void OnHoge(object sender, HogeEventArgs e) { Console.WriteLine("Hogeイベントが発生しました。"); } }
これで準備完了。以下のように使います。
static void Main(string[] args) { var source = new HogeEventSource(); var listener = new HogeEventListener(); listener.RegisterHogeHandler(source); source.RaiseHoge(); //これは受信される //リスナの参照を削除 listener = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); source.RaiseHoge(); //これは受信されない }
ここからが本題。
リスナクラスでReceiveWeakEventメソッドを実装するときに、
メソッドからfalseを返すと、冒頭の例外が発生するらしい。
まあ普通はReceiveWeakEventメソッドからfalseを返すケースは
コーディングミスでしかないと思うのでそれは良いのだけど、
いきなりこの例外が出たときに、これが原因になるって情報が
全然見つけられなかったのでハマった。
結局ググって見つかったのは以下。
WeakEventManager throws FatalExecutionEngineError when IWeakEventListener.ReceiveWeakEvent method returns false
「仕様です」との回答のようだ。
そうだと分かっていれば別に良いのだけど、
この例外がリスナのReceiveWeakEventメソッドからでなくて
WeakEventManagerのDeliverEventメソッドから発生してるように見えるので
とても分かりにくかった。
個人的には、ReceiveWeakEventからはfalseを返さないで
代わりにArgumentExceptionとか、NotSupportedExceptionとか?を
自分で投げるようにしておくほうが分かりやすいかなと思ったり。