paint-brush
非同期 EventHandler の単純なセーフティ ネット@devleader
816 測定値
816 測定値

非同期 EventHandler の単純なセーフティ ネット

Dev Leader8m2023/02/14
Read on Terminal Reader

長すぎる; 読むには

Async void は、恐ろしい非同期 EventHandler のセットアップを許可しているように見える唯一の例外です。この記事では、独自のコードで試すことができる別のソリューションを紹介します。それが理にかなっているかどうかを判断できるように、それをどのように使用できるかに関して、私の観点から長所と短所に対処します.
featured image - 非同期 EventHandler の単純なセーフティ ネット
Dev Leader HackerNoon profile picture

非同期 EventHandler について議論するとき、私たちの多くが最初に頭に浮かぶのは、それが唯一の例外であるということです。恐ろしい async void セットアップ.


以前にこれについて書いたとき、(残りの髪を引き抜きたくない) 非同期ボイドの存在を実際に許可することを含む解決策を模索していたことに興奮しました。


私にとって、これは、問題を完全に回避するためのソリューションを提供するというよりも、非同期の EventHandler を克服するために使用できるいくつかの巧妙なトリックに関するものでした。


とはいえ、この記事には多くの注目が集まっており、非常に感謝しています。一部の人々は、非同期 EventHandler を別の方法で解決したいという意見を表明しました。


これは素晴らしい点だと思ったので、async void を修正しない代わりのアプローチを思いつきたいと思いましたが、いくつかの課題を解決しながら、a-void を完全に回避できます (私がそこで何をしたかを参照してください)。非同期の EventHandler を使用します。


この記事では、独自のコードで試すことができる別のソリューションを紹介します。どのように使用できるかに関して、私の観点から長所と短所を取り上げます。これにより、ユース ケースに適しているかどうかを判断できます。


.NET fiddle right で対話可能なコードを見つけることもできますこっちに.それ以外の場合は、次のことができますGitHub でコードを確認してくださいローカルにクローンを作成して試してみたい場合。

コンパニオン動画!

ここをクリックビデオをチェックしてください!

問題

非同期 EventHandler で直面する問題は、デフォルトで C# でサブスクライブできるイベントのシグネチャが次のようになることです。


 void TheObject_TheEvent(object sender, EventArgs e);


そして、この署名の前を void にすることで、イベントをサブスクライブするために、独自のハンドラーで void を使用する必要があることに気付くでしょう。


これは、ハンドラーに async/await コードを実行させたい場合は、void メソッド内で await を実行する必要があることを意味します。これにより、疫病のように避けるように言われている、恐ろしい async void パターンが導入されます。


なぜ? async void は、例外が適切にバブルアップする機能を壊し、結果として多くの頭痛の種を引き起こす可能性があるためです。


前の記事物事の呼び出し側で創造的になることができるようにすることでこれに対処しましたが…


  • イベントの呼び出しを制御しないオブジェクトでこれをサポートする必要があるかもしれません (つまり、お気に入りの UI フレームワークでボタンのクリック イベントに接続している場合など)。


  • 一部の人々は、そのソリューション内でのコンテキストの使用をハックと見なしています (私もそれに反対していません)。


  • … 具体的には、イベント ハンドラーを使用すると、非同期の EventHandler をサポートするために実行できる他のいくつかの簡単なトリックがあります。


私の意見では、単純な方が良いと思います。したがって、async void に関する以前の記事を読んで、目的が本当に EventHandler を処理することだけだった場合、これは役立つはずです。

Try/Catch による非同期 EventHandler の解決

前述の条件に基づいて、例外処理は async void の境界を越えて中断します。この境界を越えてバブルアップする必要がある例外がある場合は、楽しい時間を過ごすことができます。


おもしろいというのは、何かが機能しない理由をデバッグするのが好きで、何が壊れているのか明確な兆候がない場合は、本当に楽しい時間を過ごせるということです。


では、これを修正する最も簡単な方法は何ですか?


アクセスできる単純なツールである try/catch を使用して、例外が最初にこの境界を越えることができないようにしましょう。


 objectThatRaisesEvent.TheEvent += async (s, e) => { // if the try catch surrounds EVERYTHING in the handler, no exception can bubble up try { await SomeTaskYouWantToAwait(); } catch (Exception ex) { // TODO: put your exception handling stuff here } // no exception can escape here if the try/catch surrounds the entire handler body }


上記のコードで説明したように、イベント ハンドラーの本体全体に try/catch ブロックを配置すると、例外がその async void 境界を越えて発生するのを防ぐことができます。表面的には、これは非常に単純で、これを実装するのに特別なことは必要ありません。


長所:

  • 非常にシンプルです。複雑なメカニズムを理解する必要はありません。


  • パッケージは必要ありません。


  • これが機能するために、イベントを発生させるクラスの所有者である必要はありません。これは、このアプローチが、WinForms や WPF UI コンポーネントを含むすべての既存のイベント発生オブジェクトで機能することを意味します。


短所:

  • これを行うことを覚えておく必要があります…どこでも。


  • コードが時間の経過とともに進化するにつれて、誰かが誤ってイベント ハンドラーの try-catch の外部にロジックを書き込んで、例外をスローする可能性があります。


そうは言っても、このソリューションは実にシンプルですが、もう少し改善できると思います。

非同期 EventHandler を改善するための (少し) 手の込んだアプローチ

最初に提案された解決策を改善できると私が考える 1 つの改善点は、例外のバブリングから安全な非同期 EventHandler があることをもう少し明確にすることができるということです。


このアプローチにより、時間の経過に伴うコードのドリフトによって、問題のあるコードがイベント ハンドラーの外部で実行されることも防止されます。ただし、これを手動で追加することを忘れないでください!


コードをチェックしてみましょう:

 static class EventHandlers { public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Action<Exception> errorHandler) where TArgs : EventArgs => TryAsync<TArgs>( callback, ex => { errorHandler.Invoke(ex); return Task.CompletedTask; }); public static EventHandler<TArgs> TryAsync<TArgs>( Func<object, TArgs, Task> callback, Func<Exception, Task> errorHandler) where TArgs : EventArgs { return new EventHandler<TArgs>(async (object s, TArgs e) => { try { await callback.Invoke(s, e); } catch (Exception ex) { await errorHandler.Invoke(ex); } }); } }


上記のコードは、まったく同じアプローチを使用して、例外が async void 境界を超えるのを防ぎます。イベント ハンドラーの本体をキャッチしようとしているだけですが、再利用するために明示的に専用のメソッドにまとめました。


これを適用すると、次のようになります。


 someEventRaisingObject.TheEvent += EventHandlers.TryAsync<EventArgs>( async (s, e) => { Console.WriteLine("Starting the event handler..."); await SomeTaskToAwait(); Console.WriteLine("Event handler completed."); }, ex => Console.WriteLine($"[TryAsync Error Callback] Our exception handler caught: {ex}"));


これで、動作する非同期タスク シグネチャを持つデリゲートができたことがわかります。その中に入れたものはすべて、前に見たヘルパー メソッド内に try/catch があるので安心です。


以下は、例外を適切にキャプチャするエラー ハンドラ コールバックを示すスクリーンショットです。


非同期 EventHandler のサンプル プログラムの出力

長所:


  • それでも非常にシンプルです。ラッパー関数は *少し* より複雑ですが、それでも非常に基本的です。


  • パッケージは必要ありません。


  • これが機能するために、イベントを発生させるクラスの所有者である必要はありません。これは、このアプローチが、WinForms や WPF UI コンポーネントを含むすべての既存のイベント発生オブジェクトで機能することを意味します。


  • ハンドラーをイベントに接続するときの構文により、非同期 EventHandler を使用する場合の意図はより明確になります。


  • 最終的により多くの例外をスローするコード ドリフトは、引き続き try/catch 内にラップされます。


短所:


  • あなたはまだこのことを接続することを覚えておく必要があります!

非同期 EventHandler に関するまとめ

もともと私は探検に着手しましたが、 async void を処理する興味深い方法、読者のフィードバックは、例が非同期の EventHandlers に焦点を当てているという点で有効であり、確かにもっと簡単な方法があるに違いありません。


この記事では、非同期 EventHandler を適切に動作させるための最も簡単な方法であり、(私の意見では) 洗練されたソリューションには、それを使用するために覚えておく必要があるという欠点しかありません。


コメント投稿者は、調査できることを示唆していましたアスペクト指向プログラミング(AoP)アプリケーション全体にこの種の動作を挿入して、忘れずに実行する必要がないようにします。


コンパイル時の AoP フレームワークがいくつか存在しますが、それは読者の演習として残しておきます (これは、私がフォローアップするための演習でもあるためです)。