5

我使用Simple Injector作为IoC容器。 SimpleInjector使用this simple technique to handle mixed life style for Per Thread and Per Web Request使用Parallel.ForEach的多线程依赖关系

container.RegisterPerWebRequest<IWebRepository, Repository>(); 
container.RegisterLifetimeScope<IThreadRepository, Repository>(); 
container.Register<IRepository>(container.GetInstance<Repository>()); 

// Register as hybrid PerWebRequest/PerLifetimeScope. 
container.Register<Repository>(() => 
{ 
    Repository repository; 
    if (HttpContext.Current != null) 
     repository = (Repository)container.GetInstance<IWebRepository>(); 
    else 
     repository = (Repository)container.GetInstance<IThreadRepository>(); 

    return repository; 
}); 

不幸的是(和明显!),其他地方在我的UnitOfWork类,这是给我的一个问题,当我使用Parallel.ForEach并尝试调用到库类的多个实例并行,因为只有第一个线程发现在HttpContext.Current

using (TransactionScope scope = new TransactionScope()) 
{ 
    Parallel.ForEach(new List<IRepository>() { _repository1, _repository2 ... }, 
     (repository) => 
     { 
      repository.Commit(); 
     }); 
    scope.Complete(); 
} 

值现在,我已经写完出了问题,我可以看到我可能缘木求鱼或一些愚蠢的事......但什么是地狱。 ..这可以做到吗?单个请求/线程注册是否可用于多个内部线程?

回答

7

通过依赖注入,您可以尝试集中关于对象生命周期的知识。这个集中的地方叫做Composition Root。当你开始将依赖关系从一个线程传递给另一个线程时,代码的这些部分必须知道传递这些依赖关系是否安全。例如,那些依赖是线程安全的吗?这些依赖项可以在新的上下文中运行吗(例如在HTTP或WCF请求之外)?在许多情况下,这可能是微不足道的,但是可以阻止你用其他实现来改变这些依赖关系,因为现在你必须记住你的代码中存在这样的地方,你需要知道哪些依赖被传递。您再次分散了这些知识,使得更难推理DI配置的正确性,并且容易导致错误配置容器,从而导致代码直接(最好)失败或导致难以调试竞争条件(最差)。

因此,让每个新启动的线程通过询问容器来创建一个新的对象图是最安全的。不要将依赖关系(即:由容器管理和注入的实例)从一个线程传递到另一个线程。

在你的情况下,你甚至似乎遇到了问题,因为你的存储库似乎与http上下文有关系,因为它们似乎不能传输给其他线程。在这种情况下,这很简单:不要这样做;-)

这并不意味着您无法以任何方式进行多线程以加快性能。但是,您必须确保此操作不会将依赖关系从一个线程移动到线程,或者它们执行时;请确保此代码(在您的案例中是Parallel.ForEach)位于应用程序的Composition Root内部,因为这是唯一可以/应该知道此操作是否可以安全执行的地方。

但是,在您的具体情况下,我发现您通过多个线程提交时非常可怕。该单位的工作不应该是原子性的吗?当你在不同的线程上执行仓库提交时,你确定这个提交仍然是原子吗?当其中一个提交失败而其他人成功时会发生什么?你如何回滚已经成功的操作?我认为你可以(并且你可能已经)解决了这些问题,但是这种解决方案给系统增加了很多额外的复杂性。您必须认真地问自己,性能改进是否能够补偿您添加到系统中的额外复杂性。

您可以在多线程应用程序here中找到关于使用依赖注入的更多信息。