[解決済み] C#でイベントを待ち受けるには?
質問
一連のイベントを持つクラスを作成していますが、そのうちの1つは
GameShuttingDown
. このイベントが発生したとき、イベントハンドラを呼び出す必要があります。このイベントのポイントは、ゲームがシャットダウンされ、データを保存する必要があることをユーザーに通知することです。保存は待機させることができますが、イベントはそうではありません。したがって、ハンドラーが呼び出されると、待機中のハンドラーが完了する前にゲームがシャットダウンされます。
public event EventHandler<EventArgs> GameShuttingDown;
public virtual async Task ShutdownGame()
{
await this.NotifyGameShuttingDown();
await this.SaveWorlds();
this.NotifyGameShutDown();
}
private async Task SaveWorlds()
{
foreach (DefaultWorld world in this.Worlds)
{
await this.worldService.SaveWorld(world);
}
}
protected virtual void NotifyGameShuttingDown()
{
var handler = this.GameShuttingDown;
if (handler == null)
{
return;
}
handler(this, new EventArgs());
}
イベント登録
// The game gets shut down before this completes because of the nature of how events work
DefaultGame.GameShuttingDown += async (sender, args) => await this.repo.Save(blah);
イベントに対するシグネチャは
void EventName
であるため、非同期化することは基本的に火をつけて忘れることです。私のエンジンは、サードパーティの開発者 (および複数の内部コンポーネント) にエンジン内でイベントが起こっていることを通知し、それらに反応させるために、イベント処理を多用しています。
私が使用できる非同期ベースの何かでイベントを置き換えるために進むべき良いルートはありますか? 私が使用できる非同期ベースの何かにイベントを置き換えるために進むべき良いルートはありますか?
BeginShutdownGame
と
EndShutdownGame
というのは、コールバックを渡すことができるのは呼び出し元だけで、エンジンにプラグインするサードパーティのものは渡せないからです(これはイベントで得られるものです)。もしサーバーが
game.ShutdownGame()
を呼び出した場合、エンジン プラグインやエンジン内の他のコンポーネントがコールバックを渡す方法はありません。
これで行くために望ましい/推奨されるルートに関する任意のアドバイスは、非常に高く評価されます! 私は周りを見回し、私が見た大部分は Begin/End アプローチを使用しており、私がやりたいことを満たすとは思えません。
編集
私が考えている別のオプションは、待機状態のコールバックを受け取る登録メソッドを使用することです。私はすべてのコールバックを繰り返し、それらのTaskを取得します。
WhenAll
.
private List<Func<Task>> ShutdownCallbacks = new List<Func<Task>>();
public void RegisterShutdownCallback(Func<Task> callback)
{
this.ShutdownCallbacks.Add(callback);
}
public async Task Shutdown()
{
var callbackTasks = new List<Task>();
foreach(var callback in this.ShutdownCallbacks)
{
callbackTasks.Add(callback());
}
await Task.WhenAll(callbackTasks);
}
どのように解決するのですか?
個人的には
async
イベント ハンドラを持つことは、最良のデザイン選択ではないかもしれません。同期ハンドラでは、いつ完了するかを知るのは簡単です。
とはいえ、何らかの理由でこのデザインにこだわらなければならない、あるいは少なくとも強くこだわらざるを得ないのであれば、それを
await
-のようなフレンドリーな方法で行うことができます。
ハンドラを登録するあなたのアイデアと
await
を登録するのは良いアイデアです。しかし、私は既存のイベントパラダイムにこだわることをお勧めします。そうすれば、コードの中でイベントの表現力を保つことができるからです。主なものは、標準的な
EventHandler
-をベースとしたデリゲートタイプを使用し、そのデリゲートタイプが返す
Task
が使えるように
await
というハンドラを作成します。
以下は、私が言いたいことを説明する簡単な例です。
class A
{
public event Func<object, EventArgs, Task> Shutdown;
public async Task OnShutdown()
{
Func<object, EventArgs, Task> handler = Shutdown;
if (handler == null)
{
return;
}
Delegate[] invocationList = handler.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, EventArgs, Task>)invocationList[i])(this, EventArgs.Empty);
}
await Task.WhenAll(handlerTasks);
}
}
は
OnShutdown()
メソッドは、標準的な "get local copy of the event delegate instance" を行った後、最初にすべてのハンドラを起動し、次に返されたすべての
Tasks
(ハンドラが呼び出されたときにローカル配列に保存されます)。
以下は、使い方を説明する短いコンソールプログラムです。
class Program
{
static void Main(string[] args)
{
A a = new A();
a.Shutdown += Handler1;
a.Shutdown += Handler2;
a.Shutdown += Handler3;
a.OnShutdown().Wait();
}
static async Task Handler1(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #1");
await Task.Delay(1000);
Console.WriteLine("Done with shutdown handler #1");
}
static async Task Handler2(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #2");
await Task.Delay(5000);
Console.WriteLine("Done with shutdown handler #2");
}
static async Task Handler3(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #3");
await Task.Delay(2000);
Console.WriteLine("Done with shutdown handler #3");
}
}
この例を見て、私は今、C#がこれを少し抽象化する方法はなかったのだろうかと思うようになりました。もしかしたら、それはあまりにも複雑な変更かもしれませんが、現在の旧式の
void
-を返すイベントハンドラと、新しい
async
/
await
の機能は少し厄介に見えます。上記は動作しますが(そしてうまく動作しています、IMHO)、このシナリオに対してより良いCLRや言語サポートがあればよかったと思います(つまり、マルチキャストデリゲートを待ち、C#コンパイラがその呼び出しを
WhenAll()
).
関連
-
[解決済み】WebForms UnobtrusiveValidationModeは、jqueryのScriptResourceMappingを必要とする
-
[解決済み】パディングが無効で、削除できない?
-
[解決済み] enumを列挙するには
-
[解決済み] intをenumにキャストするにはどうすればよいですか?
-
[解決済み] 乱数(int)を生成する方法を教えてください。
-
[解決済み] async」と「await」の使い方とタイミング
-
[解決済み] jQuery 複数のイベントで同じ関数を起動する
-
[解決済み] async/await関数を並列に呼び出す
-
[解決済み] async/await - タスクとvoidをいつ返すか?
-
[解決済み】C#のイベントとスレッドセーフについて
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】ここで「要求URIに一致するHTTPリソースが見つかりませんでした」となるのはなぜですか?
-
[解決済み] [Entity Framework 4.1でエンティティに関連オブジェクトを追加する際に、エンティティオブジェクトをIEntityChangeTracker.の複数のインスタンスから参照できない。
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】バックスラッシュを含むパス文字列のエスケープシーケンスが認識されない件
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】値が期待した範囲に収まらない
-
[解決済み】Visual Studio: 操作を完了できませんでした。パラメータが正しくありません
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み】パラメータ付きRedirectToAction