6

我使用 Simple Injector 作为我的 IoC 容器。SimpleInjector 使用这种简单的技术来处理 Per Thread 和 Per Web Request 的混合生活方式

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 并尝试并行调用 Repository 类的多个实例时,这给了我一个问题,因为只有第一个线程在 HttpContext 中找到了一个值。当前的

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

既然我已经写完我可以看到的问题,我可能会要求不可能或愚蠢的东西......但是到底是什么......可以做到吗?单个请求/线程注册是否可用于多个内部线程?

4

1 回答 1

10

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

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

在您的情况下,您甚至似乎遇到了问题,因为您的存储库似乎与 HTTP 上下文有关系,因为它们似乎不能传输到其他线程。在那种情况下,这很“容易”:不要这样做;-)

这并不意味着您不能以任何方式进行多线程来提高性能。但是您必须确保此操作不会将依赖项从一个线程移动到另一个线程,或者何时移动;确保此代码(在您的情况下为Parallel.ForEach)位于应用程序的 Composition Root 内,因为这是唯一可以/应该知道此操作是否可以安全执行的地方。

但是,在您的特定情况下,我发现您提交多个线程非常可怕。该工作单元的提交不应该是原子的吗?当您在不同线程上执行存储库提交时,您确定此提交仍然是原子的吗?当其中一个提交失败但其他提交成功时会发生什么?如何回滚已经成功的操作?

我认为您可以(并且您可能已经)解决了这些问题,但是这种解决方案给系统增加了很多复杂性。您必须问自己性能改进是否可以弥补增加的复杂性。

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

于 2012-12-21T09:04:08.157 回答