You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#静态类状态丢失:服务器环境feed_expiration_date每次请求为null

问题描述

本地与服务器均配置8进程Web Garden,本地环境下静态类CacheManager的feed_expiration_date可正常维持状态;但服务器环境中,该DateTime?变量每次请求都会变为null。同一进程ID(如PID:1234)在有效期内的重复请求,本地可保留之前的变量值,服务器却无法实现,需要技术支持。

相关代码
public static class CacheManager
{    
    public static ClientData ClientDataCache
    {
        get
        {
            return GetClientDataCacheByID(HostInstanceID);
        }
    }
    
    public static ClientData GetClientDataCacheByID(int instance_id)
    {
        try
        {
            ClientData data = null;
            if (instance_id != 0)
                data = client_cache_data_dictionary.ContainsKey(instance_id) ? client_cache_data_dictionary[instance_id] : null;

            if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1))
                return GenerateClientData();

            return data;
        }
        catch (Exception ex)
        {
            return null;
        }
    }
    
    private static ClientData GenerateClientData()
    {
        lock (InstanceLocker)
        {
            ClientData data = null;
            int instance_id = HostInstanceID;
            if (instance_id != 0)
                data = client_cache_data_dictionary.ContainsKey(instance_id) ? client_cache_data_dictionary[instance_id] : null;

            if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1))
            {
                data = new ClientData(true);

                if (data != null && data.InstanceID > 0)
                {
                    int id = data.InstanceID;
                    HostInstanceID = id;
                    client_cache_data_dictionary[id] = data;  // To prevent race conditions, this has to happen prior to client_data_cache_lock_dictionary[id] = new object();
                    client_data_cache_lock_dictionary[id] = new object();  // This has to happen 2nd, not first

                    if (Portals.Utils.IsEC2)
                    {
                        try
                        {
                            if (!global_cache_mgr_init)
                            {
                                GlobalCacheManager.GlobalCacheManager.Initialize(GlobalCacheManager.Enumerations.SystemLocations.AdminPortal, !Portals.Utils.IsEC2 ? "us-west-2" : null);
                                global_cache_mgr_init = true;
                                GlobalCacheManager.GlobalCacheManager.Start();
                            }
                        }
                        catch (Exception ex)
                        {
                            Emails.SendAdminEmail("Error Init/Start GCM on ClientCache.cs", ex.StackTrace);
                        }
                    }
                }
                Portals.Utils.LogErrorFeed("ReadGSFromDB","true PID: " + System.Diagnostics.Process.GetCurrentProcess().Id);
            }
            else
            {
                Portals.Utils.LogErrorFeed("ReadGSFromCache", "true PID: " + System.Diagnostics.Process.GetCurrentProcess().Id);
            }
            return data;
        }
    }

    public class ClientData
    {
        public ClientData(bool initdata = false) {
            if (initdata) IntializeClientData();
        }
        private List<SurveysAndFeaturesFeedResult> surveyAndFeaturesFeeds = null;
        public DateTime date_cached { get; set; }

        public DateTime? feed_expiration_date { get;set; }
        private object surveys_features_generate_locker = new object();

        public void GenerateSurveysAndFeaturesCache(bool force_refresh = false)
        {
            if(this.InstanceID != 0)
            {
                lock(surveys_features_generate_locker)
                {
                    if (this.surveyAndFeaturesFeeds == null || force_refresh)
                    {
                        this.surveyAndFeaturesFeeds = CakeFeed.GetSurveysAndFeaturesFeedResult();
                        this.feed_expiration_date = DateTime.UtcNow.AddHours(this.global_settings.CakeFeedCacheDurationHoursCKM);
                        
                    } 
                        
                }

                if(force_refresh && this.surveyAndFeaturesFeeds != null)
                {
                    Portals.Utils.RefreshPortals();
                }
            }
        }

        public List<SurveysAndFeaturesFeedResult> SurveysAndFeaturesFeed
        {
            get
            {
                if(this.global_settings.EnablePresentIcon && (surveyAndFeaturesFeeds == null || feed_expiration_date == null || feed_expiration_date < DateTime.UtcNow))
                {
                    GenerateSurveysAndFeaturesCache();
                }
                
                return (surveyAndFeaturesFeeds == null) ? new List<SurveysAndFeaturesFeedResult>() : surveyAndFeaturesFeeds.ToList();
            }
        }
    }
}
调用方式

在handler.ashx中调用:

CacheManager.ClientDataCache.SurveysAndFeaturesFeed.OrderByDescending(x=>x.PublishDate).ToList();
问题分析与解决方案

核心原因

Web Garden下每个工作进程是独立的应用域,但你遇到的是同一进程内状态丢失,说明进程内的ClientData实例被重复重建,导致feed_expiration_date被重置为null。结合代码排查,问题点集中在:

  1. date_cached未明确初始化,默认值为DateTime.MinValue,导致GetClientDataCacheByID每次都判定缓存失效,重建ClientData。
  2. HostInstanceID在进程内未正确持久化,导致每次请求都从字典取不到缓存,生成新实例。
  3. SurveysAndFeaturesFeed的getter未先校验InstanceID有效性,若InstanceID为0,GenerateSurveysAndFeaturesCache不会执行,feed_expiration_date一直为null,陷入循环重建。

修复步骤

  1. 初始化date_cached:在ClientData.IntializeClientData方法中添加date_cached = DateTime.UtcNow;,避免默认值导致缓存失效误判。
  2. 双重检查锁定优化缓存逻辑:修改GetClientDataCacheByID,在锁内二次校验缓存,避免多线程重复创建实例:
    public static ClientData GetClientDataCacheByID(int instance_id)
    {
        try
        {
            ClientData data = null;
            if (instance_id != 0)
                data = client_cache_data_dictionary.TryGetValue(instance_id, out var cachedData) ? cachedData : null;
    
            if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1))
            {
                lock (InstanceLocker)
                {
                    if (instance_id != 0)
                        client_cache_data_dictionary.TryGetValue(instance_id, out data);
                    if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1))
                    {
                        data = GenerateClientData();
                    }
                }
            }
            return data;
        }
        catch (Exception ex)
        {
            return null;
        }
    }
    
  3. 完善缓存触发条件:修改SurveysAndFeaturesFeed的getter,先校验InstanceID有效性:
    public List<SurveysAndFeaturesFeedResult> SurveysAndFeaturesFeed
    {
        get
        {
            if(this.InstanceID != 0 && this.global_settings.EnablePresentIcon 
                && (surveyAndFeaturesFeeds == null || feed_expiration_date == null || feed_expiration_date < DateTime.UtcNow))
            {
                GenerateSurveysAndFeaturesCache();
            }
            return surveyAndFeaturesFeeds?.ToList() ?? new List<SurveysAndFeaturesFeedResult>();
        }
    }
    
  4. 日志排查:在ClientData构造函数和GenerateSurveysAndFeaturesCache中添加日志,记录InstanceID、feed_expiration_date值及进程ID,确认同一进程内是否真的在重复创建实例。

内容的提问来源于stack exchange,提问作者Asad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 10:54:36