【问题标题】:Risks of Using System.Threading.Timer in an ASP.Net/SignalR Environment在 ASP.Net/SignalR 环境中使用 System.Threading.Timer 的风险
【发布时间】:2013-11-12 03:30:47
【问题描述】:

我们在一个独立的 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 使用通常出于任何原因失控)相关的明显风险,我需要减轻这些风险,或者这可能使这种方法不可行?

【问题讨论】:

    标签: c# asp.net multithreading signalr


    【解决方案1】:

    按照您建议的方式使用Timer 没有什么特别的问题。

    但是,您的代码存在一些问题。

    首先,你有:

    var oldUsers = recentUsers
                   .Select( p => p.Value )
                   .Where( u => u.LastActionTime.AddHours( 1 ) > now );
    

    这将删除最后一次活动是在过去一小时内的任何用户。因此,您在一分钟前看到的任何人都将被删除。结果是您的recentUsers 列表可能大部分时间都是空的。充其量,它会包含至少一小时前最后一次出现的用户。

    我认为您想将其更改为 &lt;。或者,换一种方式考虑:

    .Where((now - u.LastActionTime) > TimeSpan.FromHours(1));
    

    还可能存在竞争条件,即被选择删除的用户可能会在删除实际发生之前提出请求,因此您最终会删除刚刚提出请求的用户。不过,这种竞争条件的时间窗口非常狭窄,可能不值得担心。

    【讨论】:

    • 啊,是的,吉姆你说得对。我总是这样错误地计算时间! :D 我喜欢你建议的方法,它似乎比我最初使用的方法更容易理解。至于比赛条件,这是可能的,但我并不过分担心——它不会经常发生,当它发生时也不会产生重大影响。感谢您指出这一点。干杯,
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-24
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 2012-11-26
    相关资源
    最近更新 更多