Microsoft.Practices.Unity 的一个线程安全Bug浅析

      从接触Enterprise Library开始,到现在越来越感觉的Elib的强大。虽然单独看Elib里面的Block不一定是优秀的,但是作为一个整体其优势不言而喻。更重要的是Elib是MS的团队在维护,不用担心MS会把它吃掉

      这段时间一直在Elib上进行开发,Unity也是用的最多的一个Block了。由于都是在单机开发BS系统,因此很少应用到多线程,多线程的问题也没有怎么暴露出来。以前也潜意识的认为MS会把线程安全处理好,但是今天发现其实不然。测试代码如下:

  1 class Program

 2     {
 3         static UnityContainer container;
 4         static bool stop;
 5         static void Main(string[] args)
 6         {
 7             container = new UnityContainer();
 8 
 9             var t1 = new Thread(new ThreadStart(Read));
10             var t2 = new Thread(new ThreadStart(Write));
11 
12             t1.Start();
13             t2.Start();
14 
15             Console.ReadLine();
16             stop = true;
17         }
18 
19         static void Read()
20         {
21             while (true)
22             {
23                 if (container.IsRegistered<int>("a"))
24                     Console.WriteLine("OK");
25                 Thread.Sleep(10);
26                 if (stop)
27                     break;
28             }
29         }
30 
31         static void Write()
32         {
33             int index = 0;
34             while (true)
35             {
36                 container.RegisterType<Program>(index.ToString());
37                 Console.WriteLine("Regist " + index);
38                 Thread.Sleep(10);
39                 index++;
40                 if (stop)
41                     break;
42             }
43         }
44     }


运行这个简单的sample,不久就会出现:Collection was modified; enumeration operation may not execute.异常,就是说在循环一个集合时,这个集合被另一线程修改了。这个错误在多线程编程中算比较经典的异常了。查看Unity的Regist方法的代码发现其实现其实非常简单。

   public static bool IsRegistered(this IUnityContainer container, Type typeToCheck, string nameToCheck)

        {
            Guard.ArgumentNotNull(container, 
"container");
            Guard.ArgumentNotNull(typeToCheck, 
"typeToCheck");

            var registration 
= from r in container.Registrations
                               
where r.RegisteredType == typeToCheck && r.Name == nameToCheck
                               select r;
            
return registration.FirstOrDefault() != null;
        }

 通过遍历一个Resitrations查找符合记录的条件。Registrations通过

 private void FillTypeRegistrationDictionary(IDictionary<Type, List<string>> typeRegistrations)
        {
            
if(parent != null)
            {
                parent.FillTypeRegistrationDictionary(typeRegistrations);
            }

            
foreach(Type t in registeredNames.RegisteredTypes)
            {
                
if(!typeRegistrations.ContainsKey(t))
                {
                    typeRegistrations[t] 
= new List<string>();
                }

                typeRegistrations[t] 
=
                    (typeRegistrations[t].Concat(registeredNames.GetKeys(t))).Distinct().ToList();
            }

        } 

 方法得到。到这里就可看到整个实现的核心所在:NamedTypesRegistry类型的registeredNames。这个类负责类型的注册,所有的注册信息都保存在这个类中。重点就在 public void RegisterType(Type t, string name)

        {
            
if(!registeredKeys.ContainsKey(t))
            {
                registeredKeys[t] 
= new List<string>();
            }

            RemoveMatchingKeys(t, name);
            registeredKeys[t].Add(name);
        }

 RegisterType方法,这个方法根本没有考虑到多线程同步问题直接对registeredKeys进行Add操作,同样在RegisteredTypes属性中也没有进行同步处理。很显然读写异步的情况肯定会出现上面所说的异常了。

虽然可以通过在外部调用Regist和IsRegist方法进行同步处理,但是这样的话调用外层的Lock就比较麻烦而且比较分散,性能也可能有问题。

 

 

 

posted on 2011-05-30 17:18  Kain  阅读(1910)  评论(7编辑  收藏  举报

导航