前回の シンプルなチャットをつくる ランキングを偽装させないゲームをつくる準備 ではテキストの送信をしましたが、今回はキー操作とタイマーイベントの処理をおこないます。
キーを押すと○○が押されましたと表示され、離されると○○が離されましたと文字列が表示され、しばらくすると文字列が消えます。また下の行では1秒ごとにカウントアップされているのがわかります。同じページを複数のタブで開くとそれぞれ別にカウントしていることがわかります。
KeyTimerTest1クラスを作ります。名前空間は前回と同じ SignalRChat.Hubsです。
SendMessageメソッドを削除してDownKeyメソッドとDownUpメソッドを追加しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class KeyTimerTest1 : Hub { string GroupName = "KeyTimerTest1"; // 接続成功時の処理 public override async Task OnConnectedAsync() { await Clients.Caller.SendAsync("ReceiveMessage", "接続成功", Context.ConnectionId, GetDateText()); await Groups.AddToGroupAsync(Context.ConnectionId, GroupName); await base.OnConnectedAsync(); } // 切断時の処理 public override async Task OnDisconnectedAsync(Exception? exception) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupName); await base.OnDisconnectedAsync(exception); } // 前回と同じ string GetDateText() { DateTime now = System.DateTime.Now; return String.Format("{0:0000}-{1:00}-{2:00} {3:00}:{4:00}:{5:00}", now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); } // これも前回と同じ string GetSubstring(string str, int maxlength) { if (str.Length > maxlength) return str.Substring(0, maxlength); else return str; } // キーが押されたときの処理 public async Task DownKey(string key) { // 長大なデータが送りつけられるかもしれないので対策 key = GetSubstring(key, 32); // 第三引数がtrueなら押されたとき、falseなら離されたとき await Clients.Caller.SendAsync("ReceiveKeyResult", key, true); } // キーが離されたときの処理 public async Task UpKey(string key) { // 長大なデータが送りつけられるかもしれないので対策 key = GetSubstring(key, 32); await Clients.Caller.SendAsync("ReceiveKeyResult", key, false); } } |
次にページを作ります。
Pages\KeyTimerTest.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
@page @{ ViewData["Title"] = "キー操作送信のテスト"; Layout = "_Layout2"; string baseurl = Global.BaseUrl; } <div class="container"> <p id = "result"></p> <p id = "counter"></p> </div> <script src="@baseurl/js/signalr.js"></script> <script> "use strict"; let connection = new signalR.HubConnectionBuilder().withUrl("@baseurl/keyTimerTest1").build(); connection.start().then(function () { console.log("connection.start()"); }); let lastKey = ""; document.onkeydown = function(e){ if(lastKey == e.key) return; lastKey = e.key; connection.invoke("DownKey", e.key).catch(function (err) { return console.error(err.toString()); }); } document.onkeyup = function(e){ lastKey = ""; connection.invoke("UpKey", e.key).catch(function (err) { return console.error(err.toString()); }); } connection.on("ReceiveMessage", function (result, id, datetime) { document.getElementById("result").innerHTML = `${result}:${datetime}<br>ConnectionId:${id}`; }); let count = 0; connection.on("ReceiveKeyResult", function (key, isdown) { document.title = key + isdown; let text = key; if(isdown) text = text + " が押されました。" + (count++); else text = text + " が離されました。"; document.getElementById("result").textContent = text; }); </script> |
あとはサーバーでタイマーを動作させ、そのときにデータの変更点を通知することができればゲームができそうです。
タイマーを生成して1秒おきにデータを送信しようとして以下のようなコードを書いてみたのですが動きません。
<例外の画像>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class KeyTimerTest1 : Hub { public override async Task OnConnectedAsync() { await Clients.Caller.SendAsync("ReceiveMessage", "接続成功", Context.ConnectionId, GetDateText()); await Groups.AddToGroupAsync(Context.ConnectionId, GroupName); await base.OnConnectedAsync(); // タイマーを生成して1秒おきにデータを送信しようとしているのだが・・・ System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = 1000; timer.Elapsed += Timer_Elapsed; timer.Start(); } private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { Task.Run(async () => { // ここで例外が連続発生 await Clients.Caller.SendAsync("ReceiveUpdate", "伝えたいデータ"); }); } } |
実は同じような質問がStack Overflowでもされていました。
c# – SignalR: System.ObjectDisposedException with timer – Stack Overflow
Don’t store state in a property on the hub class. Every hub method call is executed on a new hub instance.
ハブクラスのプロパティに状態を保存しないでください。 すべてのハブメソッド呼び出しは、新しいハブインスタンスで実行されます。
状態を保存するなといわれても…、ところが静的なフィールド変数であればどうでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class KeyTimerTest1 : Hub { string GroupName = "KeyTimerTest1"; string ConnectionID = ""; int Counter = 0; static Dictionary<string, System.Timers.Timer> TimerMap = new Dictionary<string, System.Timers.Timer>(); static Dictionary<string, IClientProxy> ClientProxyMap = new Dictionary<string, IClientProxy>(); public override async Task OnConnectedAsync() { if (Global.IsDebug) Console.WriteLine("接続しました!!:" + Context.ConnectionId); await Clients.Caller.SendAsync("ReceiveMessage", "接続成功", Context.ConnectionId, GetDateText()); await Groups.AddToGroupAsync(Context.ConnectionId, GroupName); await base.OnConnectedAsync(); // ConnectionIDをキーにしてタイマーとIClientProxyを辞書にして保存する ClientProxyMap.Add(Context.ConnectionId, Clients.Caller); ConnectionID = Context.ConnectionId; System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = 1000; timer.Elapsed += Timer_Elapsed; timer.Start(); TimerMap.Add(Context.ConnectionId, timer); } private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { Task.Run(async () => { // イベントが発生した回数をカウントする Counter++; Console.WriteLine(Counter.ToString() + ":" + ConnectionID + ":タイマーが動いています。"); await ClientProxyMap[ConnectionID].SendAsync("ReceiveUpdate", Counter.ToString()); }); } public override async Task OnDisconnectedAsync(Exception? exception) { // 辞書のなかからタイマーを探してStopしたあとDisposeする。辞書からもRemoveする。 await Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupName); await base.OnDisconnectedAsync(exception); System.Timers.Timer timer = TimerMap[Context.ConnectionId]; timer.Stop(); timer.Dispose(); TimerMap.Remove(Context.ConnectionId); } } |
これだとブラウザを閉じたり別のページに移動するとタイマーは止まってくれます。またクライアントサイドでReceiveUpdate関数を呼び出すようにしておくと1秒ごとにカウントアップされているのがわかります。
Pages\KeyTimerTest.cshtml
1 2 3 4 5 |
// JavaScript部分 connection.on("ReceiveUpdate", function (data) { document.getElementById("counter").textContent = data; }); |
キーを押すと○○が押されましたと表示され、離されると○○が離されましたと文字列が表示され、しばらくすると文字列が消えます。また下の行では1秒ごとにカウントアップされているのがわかります。同じページを複数のタブで開くとそれぞれ別にカウントしていることがわかります。