asp.net Web サイトにログインする組織があり、各メンバーがログインするときに、SignalR Hub OnConnected オーバーライドで OrganizationMembers という名前の静的 ConcurrentDictionary に ConnectionId と OrganizationId を追加します。これを行うのは、その組織 (SignalR グループ) にのみ関連するデータを含むページ コールバック関数にデータを送信できるようにするためです。OrganizationId は、データの送信先の SignalR グループを表します。
また、OnConnnected メソッドで、OrganizationId と OrganizationId.ToString() を、一意の組織 ID を表す別の ConcurrentDictionary に追加します。OrganizationId.ToString() を格納する理由については後で説明します。一意の組織 ID を保存して、メソッドを呼び出して何度もスリープするバックグラウンド タスクで、一意の組織 ID を列挙し、それに関連する各組織 (SignalR グループ) データのみを送信できるようにします。
OnDisconnected Hub オーバーライドでは、接続を削除した後、OrganizationMembers ConcurrentDictionary の OrganizationId 値をチェックして、その OrganizationId を持つメンバーが最後に切断されたかどうかを確認し、そうであれば UniqueOrganizations ディクショナリから削除します。私は辞書 Values.Contains() が O(n) であることを知っているので、これを避けたいと思います。
これは、タスクが UniqueOrganizations を列挙するときに、たとえば同じ組織の 5 人のメンバーがログインしたが、後ですべてのメンバーを閉じる場合に、不必要にデータを送信しようとしている組織 (SignalR グループ) が存在しないようにするためです。そのグループ データを SignalR コールバック経由で送信しようとはしません。
確かに私は SignalR Hub の内部の仕組みを知らないので、すべて切断されている組織 (SignalR グループ) のメンバーに不必要にデータを送信しようとしても問題にならない場合があります。
これは SignalR Hub を考えすぎですか? 最後の組織 (SignalR グループ) メンバーがハブから切断されたかどうかを判断する必要はなく、UniqueOrganizations から OrganizationId を削除する必要はありませんか?
これで問題ない場合、O(n) であるため、Values.Contains() 辞書を回避するにはどうすればよいですか?
// the key is the ConnectionId of an Organization Member
// the value is the OrganizationId of the Member
protected static ConcurrentDictionary<string, int> _organizationMembers;
public static ConcurrentDictionary<string, int> OrganizationMembers {
get {
if(_organizationMembers == null) {
_organizationMembers = new ConcurrentDictionary<string, int>();
}
return _organizationMembers;
}
}
// the key is the OrganizationId to send the specific data to
// the value is the OrganizationId converted to string
protected static ConcurrentDictionary<int, string> _uniqueOrganizations;
public static ConcurrentDictionary<int, string> UniqueOrganizations {
get {
if(_uniqueOrganizations == null) {
_uniqueOrganizations = new ConcurrentDictionary<int, string>();
}
return _uniqueOrganizations;
}
}
// Hub Code
public override Task OnConnected() {
string connectionId = Context.ConnectionId;
string organizationId = Context.Request.QueryString["organizationId"];
int organizationIdValue = int.Parse(organizationId);
OrganizationMembers.TryAdd(connectionId, organizationIdValue);
UniqueOrganizations.TryAdd(organizationIdValue, organizationId);
// the organizationId represents the SignalR group
Groups.Add(connectionId, organizationId);
return base.OnConnected();
}
public override Task OnDisconnected() {
string organizationId = string.Empty;
int organizationIdValue;
string connectionId = Context.ConnectionId;
OrganizationMembers.TryRemove(connectionId, out organizationIdValue);
// if that happens to be the last Member connection to be removed
// then remove the OrganizationId from the unique OrganizationIds
// so it won't be a wasted enumeration and useless callback
// I want to avoid this O(n) Contains()
if(!OrganizationMembers.Values.Contains(organizationIdValue)) {
UniqueOrganizations.TryRemove(organizationIdValue, out organizationId);
}
Groups.Remove(connectionId, organizationId);
return base.OnDisconnected();
}
// Task code
foreach(int organizationIdValue in DataCache.UniqueOrganizations.Keys) {
// this is why I also stored the OrganizationId as a string so I wouldn't have to
// convert it to a string each time the dictionary is enumerated.
// I can just have the organizationId string ready to use as the SignalR Group.
string organizationId = UniqueOrganizations[organizationIdValue];
try {
string organizationData = GetOrganizationData(organizationIdValue);
_clients.Group(organizationId).sendToOrganizationData(organizationData);
}
catch(Exception ex) {
_clients.Group(organizationId).sendToOrganizationError();
}
}