最近在早上更新公司网站的客户管理系统的程序代码,更新过后对网站的页面进行了简单的访问测试,都正常,没有问题。就没在管。可是过了会,有人反映网站打开很慢。我于是赶快检查,确实很慢,半天打不开。然后用远程桌面打开服务器,也是很慢,过了好一会才进到服务器,打开任务管理器,发觉客户管理系统所在的进程w3wp.exe cpu占用高达100% 。观察了一会,一直如此,因为并不清楚问题所在,但是这个问题必须马上解决。尝试结束此进程,结束后cpu 占用马上下来了。这个时候系统马上重新开启了一个客户管理系统所在网站的新进程。此时再访问客户管理系统网站,一切都正常。观察了一段时间,不再出现cpu 占用100% 的问题。

在windows 事件查看器中查看应用程序日志,发现了sqlserver大量超时信息。这说明不了什么,因为w3wp.exe 把cpu都占用了,自然超时。其他的就是发现了另一个网站 有关redis的操作出现了错误。难道是这里的问题?可又觉得不像,一个网站出现的问题怎么会影响另一个,又不是在一个进程中。只有先放放了。

第二天上午,由于客服管理系统有新内容更新,再次更新,这次更新过后,同样的问题又出现了,结束进程后,此问题又消失。看了windows 事件日志,还是和昨天差不多的样子。这次我查了下错误日志,发觉大致有3个错误比较多。第一个:服务器无法在已发送 HTTP 标头之后设置状态,因为这个错误其他网站MVC类的网站也有,应该不是cpu 占用100% 导致的。第二个错误是:未将对象引用设置到对象的实例,是在访问/service/GetReviewMode 出现的,查了下这个action 的写法,感觉没什么问题 。 第三个错误:

已添加了具有相同键的项 ,同样是在访问/service/GetReviewMode 出现的。也没看出什么问题。决定再放一放看。
第三天上午,再次更新网站,同样的问题又出现了。结束进程,问题消失。但这次却不能掉以轻心,第三次出现,而且是因为在更新时出现,前两次还可以解释为凑巧,这次却再不能这样解释了。而我看了错误日志,仍然是访问/service/GetReviewMode 导致两个错误,一个是未将对象引用设置到对象的实例,另一个是已添加了具有相同键的项。这次我仔细看了这两个错误的堆栈提示。
其中未将对象引用设置为对象实例的有两种,第一种:
在 SXF.Utils.EnumDescription.GetFieldTexts(Type enumType, SortType sortType)
   在 SXF.Utils.EnumDescription.GetFieldTexts(Type enumType)
   在 SXF.Utils.EnumDescription.GetFieldText(Object enumValue)
   在 LMSoft.Web.Controllers.ServiceController.GetReviewMode(Int32 status)
第二种:
在 System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   在 SXF.Utils.EnumDescription.GetFieldTexts(Type enumType, SortType sortType)
   在 SXF.Utils.EnumDescription.GetFieldTexts(Type enumType)
   在 SXF.Utils.EnumDescription.GetFieldText(Object enumValue)
   在 LMSoft.Web.Controllers.ServiceController.GetReviewMode(Int32 status)
	
已添加了具有相同键的项的堆栈提示是:
在 System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   在 System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   在 SXF.Utils.EnumDescription.GetFieldTexts(Type enumType, SortType sortType)
   在 SXF.Utils.EnumDescription.GetFieldTexts(Type enumType)
   在 SXF.Utils.EnumDescription.GetFieldText(Object enumValue)
   在 LMSoft.Web.Controllers.ServiceController.GetReviewMode(Int32 status)
这三个错误都指向了一个action :GetReviewMode的同一个方法:GetFieldText ,难道这个方法有问题?于是百度,并没有搜索到可用的信息。于是我搜索:
System.Collections.Generic.Dictionary`2.Insert, 这次搜索到一篇文章,介绍说在单线程的时候C# dictionary插入数据,不会出现问题,多线程的时候会出现未将对象引用设置为对象实例的错误。我这个方法确实是向dictionary中插入数据了,难道是这里的问题?于是我进行分析,此action 是我在分页中用ajax调用的,用来显示回访状态,并且每页显示10行,在加载的时候要调用10次,如果公司有多个人同时访问这个列表页面,确实可能导致并发问题。于是我分析GetFieldText 方法代码:
/// <summary>
        /// 获得指定枚举类型中,指定值的描述文本
        /// </summary>
        /// <param name="enumValue">枚举值,不要作任何类型转换</param>
        /// <returns>描述字符串</returns>
        public static string GetFieldText(object enumValue)
        {
            List<EnumDescription> fieldTexts = GetFieldTexts(enumValue.GetType()) as List<EnumDescription>;
            if (CollectionHelper.IsNullOrEmpty<EnumDescription>(fieldTexts))
            {
                return string.Empty;
            }
            EnumDescription description = fieldTexts.Find(item => item.m_fieldIno.Name.Equals(enumValue.ToString()));
            if (description == null)
            {
                return string.Empty;
            }
            return description.Description;
        }

这个方法调用了GetFieldTexts 方法,而从堆栈输出上看,就是在这里出错的,此方法的原代码是这样写的:
/// <summary>
        /// 获取枚举类型定义的所有文本,按定义的顺序返回
        /// </summary>
        /// <param name="enumType">枚举类型</param>
        /// <returns>所有定义的文本</returns>
        public static IList<EnumDescription> GetFieldTexts(Type enumType)
        {
            return GetFieldTexts(enumType, SortType.Default);
        }
        /// <summary>
        /// 获取枚举类型定义的所有文本
        /// </summary>
        /// <param name="enumType">枚举类型</param>
        /// <param name="sortType">排序类型</param>
        /// <returns>枚举描述集合</returns>
        public static IList<EnumDescription> GetFieldTexts(Type enumType, SortType sortType)
        {
            if (!EnumDescriptionCache.ContainsKey(enumType.FullName))
            {
                FieldInfo[] fields = enumType.GetFields();
                IList<EnumDescription> list = new List<EnumDescription>();
                foreach (FieldInfo info in fields)
                {
                    object[] customAttributes = info.GetCustomAttributes(typeof(EnumDescription), false);
                    if (customAttributes.Length == 1)
                    {
                        EnumDescription item = (EnumDescription) customAttributes[0];
                        item.m_fieldIno = info;
                        list.Add(item);
                    }
                }
                EnumDescriptionCache.Add(enumType.FullName, list);
            }
            IList<EnumDescription> list2 = EnumDescriptionCache[enumType.FullName];
            if (list2.Count <= 0)
            {
                throw new NotSupportedException("枚举类型[" + enumType.Name + "]未定义属性EnumValueDescription");
            }
            if (sortType != SortType.Default)
            {
                for (int i = 0; i < list2.Count; i++)
                {
                    for (int j = i; j < list2.Count; j++)
                    {
                        bool flag = false;
                        switch (sortType)
                        {
                            case SortType.DisplayText:
                                if (string.Compare(list2[i].Description, list2[j].Description) > 0)
                                {
                                    flag = true;
                                }
                                break;

                            case SortType.Rank:
                                if (list2[i].EnumRank > list2[j].EnumRank)
                                {
                                    flag = true;
                                }
                                break;
                        }
                        if (flag)
                        {
                            EnumDescription description2 = list2[i];
                            list2[i] = list2[j];
                            list2[j] = description2;
                        }
                    }
                }
            }
            return list2;
        }

这里先判断:
if (!EnumDescriptionCache.ContainsKey(enumType.FullName))

然后在不包含的情况下,经过一系列处理插入:
EnumDescriptionCache.Add(enumType.FullName, list);
单线程下这样是没问题的,但是如果是多线程,则这样处理,没有锁定 EnumDescriptionCache 对象,却是可以导致重复插入的。我看了下 EnumDescriptionCache 的定义,此变量是静态的。
private static IDictionary<string, IList<EnumDescription>> EnumDescriptionCache = new Dictionary<string, IList<EnumDescription>>();

我们知道一个类的静态变量,是可以在不同线程间公用的。到这里,几乎可以确认就是这里的问题了。而为什么会在网站更新后才会出现这个问题,那是因为,静态变量在初始化后在整个应用程序域中一直存在,一旦dll 文件更新,才会重新初始化。知道是这里的问题,因此对GetFieldTexts 方法在更新 EnumDescriptionCache 静态变量时使用锁来进行处理,防止在处理工程中其他用户改变这个静态变量的值。代码如下:
/// <summary>
        /// 获取枚举类型定义的所有文本
        /// </summary>
        /// <param name="enumType">枚举类型</param>
        /// <param name="sortType">排序类型</param>
        /// <returns>枚举描述集合</returns>
        public static IList<EnumDescription> GetFieldTexts(Type enumType, SortType sortType)
        {
            Monitor.Enter(EnumDescriptionCache);
            try
            {
                if (!EnumDescriptionCache.ContainsKey(enumType.FullName))
                {
                    FieldInfo[] fields = enumType.GetFields();
                    IList<EnumDescription> list = new List<EnumDescription>();
                    foreach (FieldInfo info in fields)
                    {
                        object[] customAttributes = info.GetCustomAttributes(typeof(EnumDescription), false);
                        if (customAttributes.Length == 1)
                        {
                            EnumDescription item = (EnumDescription)customAttributes[0];
                            item.m_fieldIno = info;
                            list.Add(item);
                        }
                    }
                    EnumDescriptionCache.Add(enumType.FullName, list);
                }
                IList<EnumDescription> list2 = EnumDescriptionCache[enumType.FullName];
                if (list2.Count <= 0)
                {
                    throw new NotSupportedException("枚举类型[" + enumType.Name + "]未定义属性EnumValueDescription");
                }
                if (sortType != SortType.Default)
                {
                    for (int i = 0; i < list2.Count; i++)
                    {
                        for (int j = i; j < list2.Count; j++)
                        {
                            bool flag = false;
                            switch (sortType)
                            {
                                case SortType.DisplayText:
                                    if (string.Compare(list2[i].Description, list2[j].Description, StringComparison.Ordinal) > 0)
                                    {
                                        flag = true;
                                    }
                                    break;

                                case SortType.Rank:
                                    if (list2[i].EnumRank > list2[j].EnumRank)
                                    {
                                        flag = true;
                                    }
                                    break;
                            }
                            if (flag)
                            {
                                EnumDescription description2 = list2[i];
                                list2[i] = list2[j];
                                list2[j] = description2;
                            }
                        }
                    }
                }
                return list2;
            }
            finally
            {
                Monitor.Exit(EnumDescriptionCache);
            }

        }

在try 之前加入了Monitor.Enter(EnumDescriptionCache); 进行加锁,在finally 中进行解锁:Monitor.Exit(EnumDescriptionCache); 至于为什么不使用 lock 进行加锁和解锁,是因为我看到一篇文章介绍 Lock 与 Monitor 的区别,解释说:Lock关键字实际上是一个语法糖,它将Monitor对象进行封装,给object加上一个互斥锁 。也就是说 Lock 实际最终使用的仍然是Monitor 。更改代码后在本地进行了测试,然后上传到服务器,对服务器进行监控,这次更新代码,未出现客户管理系统网站cpu 占用 100% 的情况。上线,经过两天观察,没有再出现cpu 占用100% 的情况,可以肯定这个问题就是此方法中静态变量在修改的时候没有考虑到并发情况导致
本篇文章参考的资料:
http://www.cnblogs.com/chengxingliang/p/3150731.html
http://blog.csdn.net/gaobobo138968/article/details/43672785








Logo

快速构建 Web 应用程序

更多推荐