每线程生命周期非常危险,一般情况下不应该在应用程序中使用它,特别是 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是不安全的。
Singleton
生命周期,而DbContext
仅在HttpContext为空时被注册为PerThread
,这是为了Hangfire后台作业,对于普通的Web请求则使用PerRequest
。当流量增加时,我们会遇到很多死锁问题,我确信这是由于DbContext的PerThread生命周期引起的。我们正在使用Unity v5,您能为Hangfire作业提供什么样的dbContext生命周期? - ibubi