我们在一个独立的 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()
分别在应用程序生命周期的开始和结束时调用一次。它通过我们的 IoC 容器(结构图)UserStateService
提供给我们的Hub
类,并且目前没有任何特殊生命周期处理的范围(即它不是Singleton
或线程范围的,只是每个实例的请求)。
我们已经在我们的生产应用程序中使用了静态并发字典,它们运行良好,没有任何已知的性能问题实例。我不确定的是在Timer
这里运行一个循环。
所以,我的问题是,这里是否存在与线程被阻塞/锁定(或 CPU 使用通常出于任何原因失控)相关的明显风险,我需要减轻这些风险,或者这可能使这种方法不可行?