我们在一个独立的ASP.Net应用程序中运行SignalR,该应用程序在主ASP.Net网站的虚拟目录中运行。
在我们的SignalR集线器实现中,我们有一个静态ConcurrentDictionary<int, UserState>
变量,可以在各个连接上保持一些轻量级的用户状态。随着时间的推移,将根据客户端操作添加变量(即,当新用户开始与我们的网站交互时)。该变量实质上是跨连接提供一些简单的状态跟踪。
我们并不特别想添加一个特殊的SignalR背板,这需要额外的基础设施依赖性,因为我们的数据负载可能相对轻量级,并且跟踪内存应该足够了。
当用户长时间处于非活动状态(假设1小时)时,我们希望将它们从字典变量中删除。无论什么过程都应该保证在一致的基础上运行 - 因此,不依赖于用户行为,而是取决于定时持续时间。
我认为这是一个很好的解决方案:
public class UserStateService : IUserStateService
{
private static readonly ConcurrentDictionary<int, UserState> recentUsers = new ConcurrentDictionary<int, UserState>();
private static Timer timer;
public static void StartCleanup()
{
timer = new Timer( CleanupRecentUsers, null, 0, 60000 );
}
public static void StopCleanup()
{
timer.Dispose();
}
private static void CleanupRecentUsers( object state )
{
var now = DateTime.UtcNow;
var oldUsers = recentUsers.Select( p => p.Value ).Where( u => u.LastActionTime.AddHours( 1 ) > now );
foreach ( var user in oldUsers )
{
UserState removedUser;
recentUsers.TryRemove( user.UserId, out removedUser );
}
}
// other code for adding/updating user state.
}
如上所述,我认为这是一个很好的解决方案。但是,我不太熟悉线程管理(虽然我知道在ASP.Net中处理静态对象很危险)。
分别在应用程序生命周期的开始和结束时分别调用 StartCleanup()
和StopCleanup()
。 UserStateService
通过我们的IoC容器(结构图)提供给我们的Hub
类,并且当前没有任何特殊的生命周期处理范围(即它不是Singleton
或线程范围的,只是每个 - 实例请求)。
我们已经在我们的生产应用程序中使用静态并发字典,并且它们在没有任何已知性能问题的情况下工作正常。我不确定的是在这里运行Timer
循环。
所以,我的问题是,这里是否有任何明显的风险与线程被阻塞/锁定(或CPU使用通常会因任何原因失控)我需要缓解或者哪种方法可能导致这种方法不可行?
答案 0 :(得分:3)
以您建议的方式使用Timer
没有特别的问题。
但是,您的代码存在一些问题。
首先,你有:
var oldUsers = recentUsers
.Select( p => p.Value )
.Where( u => u.LastActionTime.AddHours( 1 ) > now );
这将删除最后一次活动在过去一小时内的所有用户。所以你在一分钟前看到的任何人都将被删除。结果是您的recentUsers
列表在大多数情况下可能都是空的。充其量,它将包含至少一小时前最后一次见过的用户。
我认为您要将其更改为<
。或者,以另一种方式思考:
.Where((now - u.LastActionTime) > TimeSpan.FromHours(1));
可能还存在竞争条件,即选择删除的用户可能会在删除实际发生之前发出请求,因此您最终会删除刚刚发出请求的用户。然而,这种竞争条件的时间窗口非常狭窄,可能不值得担心。