• EntityFramework用法探索(七)线程安全实践


    前文中,我们通过Unity来注册各种类型和WiringUp。

     1       IUnityContainer container = new UnityContainer()
     2         .RegisterType(typeof(IRepository<>), typeof(Repository<>), new ContainerControlledLifetimeManager())
     3         .RegisterType<IUnitOfWork, UnitOfWork>(new ContainerControlledLifetimeManager())
     4         .RegisterType<DbContext, RETAILContext>(new ContainerControlledLifetimeManager())
     5         .RegisterType<DbContextAdapter>(new ContainerControlledLifetimeManager())
     6         .RegisterType<IObjectSetFactory, DbContextAdapter>(new ContainerControlledLifetimeManager())
     7         .RegisterType<IObjectContext, DbContextAdapter>(new ContainerControlledLifetimeManager())
     8         .RegisterType<ICustomerRepository, CustomerRepository>(new ContainerControlledLifetimeManager());
     9 
    10       UnityServiceLocator locator = new UnityServiceLocator(container);
    11       ServiceLocator.SetLocatorProvider(() => locator);
    12 
    13       ICustomerRepository customerRepository = container.Resolve<ICustomerRepository>();

    但选择使用了ContainerControlledLifetimeManager对象生命周期管理器,其将每个对象存储为Singleton。这导致在多线程环境下会产生异常。

    例如我们尝试在多线程条件下更新Customer表:

     1       List<Task> tasks = new List<Task>();
     2       for (int i = 0; i < 16; i++)
     3       {
     4         DomainModels.Customer modifiedCustomer = Mapper.Map<DomainModels.Customer, DomainModels.Customer>(customer1);
     5         modifiedCustomer.Name = modifiedCustomer.Name + i;
     6 
     7         Task t = Task.Factory.StartNew(() =>
     8         {
     9           try
    10           {
    11             customerRepository.UpdateCustomer(modifiedCustomer);
    12           }
    13           catch (Exception ex)
    14           {
    15             Console.WriteLine("Exception occurred in thread " + Thread.CurrentThread.ManagedThreadId);
    16             Console.WriteLine(ex.Message);
    17           }
    18         });
    19         tasks.Add(t);
    20       }
    21       Task.WaitAll(tasks.ToArray());

    但由于我们仍然需要EntityFramework的Local功能,即在当前线程环境下始终使用当前上下文中的对象。我们可能还无法选择其他Unity对象生命期管理模型

    此时,我们考虑一种新的方法,引入线程Scope功能,即在给定线程中,使用同一个UnityContainer来维护对象,这样间接利用的EntityFramework的上下文功能。

    原理很简单,就是为每个线程生成一个单独的ChildUnityContainer。

     1   public class UnityContainerScope : IDisposable
     2   {
     3     private static ConcurrentDictionary<int, bool> scopeMapping
     4       = new ConcurrentDictionary<int, bool>();
     5 
     6     protected UnityContainerScope()
     7     {
     8       ScopeId = Thread.CurrentThread.ManagedThreadId;
     9       scopeMapping.Add(ScopeId, true);
    10     }
    11 
    12     public int ScopeId { get; private set; }
    13     public static int ScopeCount { get { return scopeMapping.Count; } }
    14 
    15     public static UnityContainerScope NewScope()
    16     {
    17       return new UnityContainerScope();
    18     }
    19 
    20     public static bool InScope(int scopeId)
    21     {
    22       return scopeMapping.ContainsKey(scopeId);
    23     }
    24 
    25     public void Dispose()
    26     {
    27       UnityContainerDispatcher.DisposeContainer();
    28       scopeMapping.Remove(ScopeId);
    29     }
    30   }

    这里同时需要一个UnityContainerDispatcher来负责为线程生成Container容器。

     1   public static class UnityContainerDispatcher
     2   {
     3     private static IUnityContainer parentContainer = null;
     4     private static ConcurrentDictionary<int, IUnityContainer> containerMapping
     5       = new ConcurrentDictionary<int, IUnityContainer>();
     6 
     7     public static void InjectParentContainer(IUnityContainer unity)
     8     {
     9       if (unity == null)
    10         throw new ArgumentNullException("unity");
    11 
    12       parentContainer = unity;
    13     }
    14 
    15     public static IUnityContainer GetContainer()
    16     {
    17       int key = Thread.CurrentThread.ManagedThreadId;
    18 
    19       if (!UnityContainerScope.InScope(key))
    20       {
    21         throw new UnityContainerNotInScopeException(
    22           string.Format(CultureInfo.InvariantCulture,
    23           "The specified scope id [{0}] is not in scope.", key));
    24       }
    25 
    26       if (!containerMapping.ContainsKey(key))
    27       {
    28         BuildUpContainer(key);
    29       }
    30 
    31       return containerMapping.Get(key);
    32     }
    33 
    34     public static void DisposeContainer()
    35     {
    36       int key = Thread.CurrentThread.ManagedThreadId;
    37       IUnityContainer container = containerMapping.Remove(key);
    38       if (container != null)
    39       {
    40         container.Dispose();
    41       }
    42     }
    43 
    44     private static void BuildUpContainer(int key)
    45     {
    46       if (parentContainer == null)
    47         throw new InvalidProgramException("The parent container cannot be null.");
    48 
    49       IUnityContainer childContainer = parentContainer.CreateChildContainer();
    50       containerMapping.Add(key, childContainer);
    51     }
    52   }

    在注入的根UnityContainer中,我们通过使用CreateChildContainer方法来获取一个新的Container,同时继承所有根容器的注册配置信息。这要求使用HierarchicalLifetimeManager生命期管理器

    此时,我们的代码修改为,

     1       IUnityContainer container = new UnityContainer()
     2         .RegisterType(typeof(IRepository<>), typeof(Repository<>), new HierarchicalLifetimeManager())
     3         .RegisterType<IUnitOfWork, UnitOfWork>(new HierarchicalLifetimeManager())
     4         .RegisterType<DbContext, RETAILContext>(new HierarchicalLifetimeManager())
     5         .RegisterType<DbContextAdapter>(new HierarchicalLifetimeManager())
     6         .RegisterType<IObjectSetFactory, DbContextAdapter>(new HierarchicalLifetimeManager())
     7         .RegisterType<IObjectContext, DbContextAdapter>(new HierarchicalLifetimeManager())
     8         .RegisterType<ICustomerRepository, CustomerRepository>(new HierarchicalLifetimeManager());
     9 
    10       UnityContainerDispatcher.InjectParentContainer(container);
    11 
    12       ICustomerRepository customerRepository = container.Resolve<ICustomerRepository>();

    创建多线程测试代码,

     1       List<Task> tasks = new List<Task>();
     2       for (int i = 0; i < 16; i++)
     3       {
     4         DomainModels.Customer modifiedCustomer = Mapper.Map<DomainModels.Customer, DomainModels.Customer>(customer1);
     5         modifiedCustomer.Name = modifiedCustomer.Name + i;
     6 
     7         Task t = Task.Factory.StartNew(() =>
     8         {
     9           try
    10           {
    11             using (UnityContainerScope scope = UnityContainerScope.NewScope())
    12             {
    13               customerRepository.UpdateCustomer(modifiedCustomer);
    14               Console.WriteLine("Modified " + modifiedCustomer.Name + " in thread " + Thread.CurrentThread.ManagedThreadId);
    15             }
    16           }
    17           catch (Exception ex)
    18           {
    19             Console.WriteLine("Exception occurred in thread " + Thread.CurrentThread.ManagedThreadId);
    20             Console.WriteLine(ex.Message);
    21           }
    22         });
    23         tasks.Add(t);
    24       }
    25       Task.WaitAll(tasks.ToArray());

    测试结果表明已经可以安全的在多线程条件下工作了。

    完整代码和索引

    EntityFramework用法探索系列

    完整代码下载

  • 相关阅读:
    Linux运维笔记
    回到顶部过渡js代码
    好想你红枣
    鼠标点击区域问题
    ie6 hover 子元素无效bug
    IE6和7下text-indent导致inline-block标签消失的bug
    星星评分js代码
    洛谷P3147 [USACO16OPEN]262144 2048 合并 倍增 动归 递推
    洛谷P1114 “非常男女”计划
    洛谷P1108 低价购买 动态规划
  • 原文地址:https://www.cnblogs.com/gaochundong/p/entityframework_usage_thread_scope.html
Copyright © 2020-2023  润新知