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用法探索系列

完整代碼下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM