何时使用PerThreadLifetimeManager?

12

我正在遵循下面链接的示例来设置Unity与我的服务层一起工作。我的项目与这篇文章中的项目非常相似,除了在注册服务依赖项时为什么要使用PerThreadLifetimeManager,我对其中的一切都很理解。请注意,我还在我的服务层中使用通用存储库和工作单元。大多数Unity示例使用默认(短暂)生命周期管理器,由于我的设置与下面的设置类似,我想知道为什么我应该使用PerThreadLifeimeManager?我正在使用ASP.NET Web Forms项目作为我的当前表示层,如果有任何更改,请告诉我。

container.RegisterType<ICatalogService, CatalogService>(
    new PerThreadLifetimeManager())

[在 ASP.NET MVC 3 中使用 EF Code First 依赖注入的仓储模式][1] [1]: http://www.dotnetage.com/publishing/home/2011/07/05/6883/the-repository-pattern-with-ef-code-first-dependeny-injection-in-asp-net-mvc3.html

1个回答

32

每线程生命周期非常危险,一般情况下不应该在应用程序中使用它,特别是 web 应用程序。

此生命周期应被视为危险,因为很难预测线程的实际寿命。当使用 new Thread().Start() 创建和启动线程时,您将获得一个新的线程静态内存块,这意味着容器会为您创建一个新的每线程实例。然而,当使用 ThreadPool.QueueUserWorkItem 从线程池中启动线程时,则可能会获取现有的池中线程。当在 ASP.NET 中运行时也是如此。ASP.NET 将线程池用于提高性能。

这意味着线程几乎总是会超出 web 请求的生命周期。另一方面,ASP.NET 可以异步运行请求,这意味着 web 请求可能在不同的线程上完成。当使用 Per Thread 生命期时,这就是一个问题。当然,当您开始使用 async/await 时,这种影响会加倍。

这是一个问题,因为您通常会在请求开始时调用 Resolve<T> 一次。这将加载完整的对象图,包括注册了 Per Thread 生命期的服务。当 ASP.NET 在不同的线程上完成请求时,这意味着解析的对象图将移动到这个新线程,包括所有已注册的 Per Thread 实例。

由于这些实例是以 Per Thread 注册的,它们可能不适合在另一个线程中使用。它们几乎肯定不是线程安全的(否则它们将被注册为 Singleton)。由于最初启动请求的第一个线程已经可以自由地选择接收新请求,因此您可能会遇到两个线程同时访问这些 Per Thread 实例的情况。这将导致竞态条件和难以诊断和查找的错误。

因此,一般来说,使用 Per Thread 是一个坏主意。相反,请使用具有明确定义范围的生命周期(隐式或显式定义了开头和结尾)。大多数 DI 框架实现的 Per Web Request 生命周期通常是隐式作用域(您无需自己结束它)。

对于您的问题特别说明

更糟的是,你提到的博客文章存在配置错误。 ICatalogService 定义为每个线程生命周期。然而,此服务依赖于一个被定义为瞬态的IDALContext服务。由于对IDALContext实例的引用存储为CatalogService中的私有字段,这意味着DALContext的生命周期与ICatalogService相同。这是一个问题,因为IDALContext被定义为瞬态,并且可能不是线程安全的。

生命周期的一般规则

一般规则是让组件只依赖于具有相等或更长生命周期的服务。因此,瞬态服务可以依赖于单例服务,但反之则不行。

由于以线程为基础注册的组件通常会长时间存活,因此通常只能安全地依赖于其他线程或单例实例。由于ASP.NET可以将单个请求分成多个线程,在ASP.NET应用程序(包括MVC、Web Forms和特别是Web API)的上下文中使用Per Thread是不安全的。


5
我在我的 Web 服务中使用了“Per-thread”,它引起了各种混乱。当流量增加时,它会导致一些异常,这些异常非常难以复现。 - Bill Heitstuman
@Steven,这篇文章已经发布很久了,但我有一个遗留项目,其中服务被注册为Singleton生命周期,而DbContext仅在HttpContext为空时被注册为PerThread,这是为了Hangfire后台作业,对于普通的Web请求则使用PerRequest。当流量增加时,我们会遇到很多死锁问题,我确信这是由于DbContext的PerThread生命周期引起的。我们正在使用Unity v5,您能为Hangfire作业提供什么样的dbContext生命周期? - ibubi
嗨@ibubi,我对Unity不是很熟悉,但我认为在Unity中,其作用域机制是通过子容器实现的。这意味着您的Hangfire基础架构应该为每个执行的作业创建一个新的子容器;从该子容器中解析和执行根服务;最后,在作业结束时处理子容器。 - Steven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接