ASP.NET Core 3框架揭秘之 异步线程无法使用IServiceProvider问题

 更新时间:2019年12月04日 11:34:28   作者:Artech  
这篇文章主要介绍了ASP.NET Core 3框架揭秘之异步线程无法使用IServiceProvider问题,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

标题反映的是上周五一个同事咨询我的问题,我觉得这是一个很好的问题。这个问题有助于我们深入理解依赖注入框架在ASP.NET Core中的应用,以及服务实例的生命周期。

一、问题重现

我们通过一个简单的实例来模拟该同事遇到的问题。我们采用极简的方式创建了如下这个ASP.NET Core MVC应用。如下面的代码片段所示,除了注册与ASP.NET Core MVC框架相关的服务与中间件之外,我们还调用了IHostBuilder的UseDefaultServiceProvider方法将配置选项ServiceProviderOptions的ValidateScopes属性设置为True,以开启针对服务范围的验证。我们还采用Scoped生命周期模式注册了服务IFoobar,具体的实现类型Foobar还实现了IDisposable接口。

public class Program
{
 public static void Main()
 {
 Host
 .CreateDefaultBuilder()
 .UseDefaultServiceProvider(options => options.ValidateScopes = true)
 .ConfigureWebHostDefaults(builder => builder
 .ConfigureLogging(logging => logging.ClearProviders())
 .ConfigureServices(services => services
  .AddScoped<IFoobar, Foobar>()
  .AddRouting()
  .AddControllers())
 .Configure(app => app
  .UseRouting()
  .UseEndpoints(endpoints => endpoints.MapControllers())))
 .Build()
 .Run();
 }
}

public interface IFoobar { }
public class Foobar : IFoobar, IDisposable
{
 public void Dispose() => Console.WriteLine("Foobar.Dispose();");
}

我们创建了如下这个HomeController,它的构造函数中注入了一个IServiceProvider对象。在Action方法Index中,我们调用Task的静态方法Run异步执行了一些操作。具体来说,在异步执行的操作中,我们利用调用上面注入的这个IServiceProvider对象的GetRequiredService<T>方法试图获取一个IFoobar服务实例。由于这段操作时在一个Try/Catch中执行的,抛出的异常消息的堆栈信息会直接输出到控制台上。

public class HomeController: Controller
{
 private readonly IServiceProvider _requestServices;
 public HomeController(IServiceProvider requestServices)
 {
 _requestServices = requestServices;
 }
 [HttpGet("/")]
 public IActionResult Index()
 {
 Task.Run(async() => {
 try
 {
 await Task.Delay(100);
 var foobar = _requestServices.GetRequiredService<IFoobar>();
 }
 catch (Exception ex)
 {
 Console.WriteLine(ex.Message);
 Console.WriteLine(ex.StackTrace);
 }
 });
 return Ok();
 }
}

在运行该应用程序后,我们利用浏览器采用根路径(“/”)对Action方法Index发起访问后,服务端控制台上会出现如下所示的错误信息。

二、ApplicationServices与RequestServices

从上图所示的错误消息可以看出,问题出在我们试图利用一个被Dispose的IServiceProvider来获取我们所需的服务实例。我们知道,ASP.NET Core应用在启动和请求处理过程中所需的服务几乎都是由代表DI容器的IServiceProvider提供的。具体来说,这里存在着两种类型的IServiceProvider对象,一种与当前应用的生命周期保持一致,我们一般将其称为ApplicationServices,另一种则是具体针对每个请求的IServiceProvider对象,我们将其称为RequestServices。

一般来说,ApplicationServices用于提供管道构建过程中所需的服务实例,具体请求处理过程中所需的服务实例一般由RequestServices提供。具体来说,对于接收的每一个请求,ASP.NET Core框架都会利用ApplicationServices创建一个代表服务范围的IServiceScope对象,后者就是对RequestServices的封装。在完成了针对请求的处理之后,服务范围被终结,RequestServices被Dispose。

对于我们演示的实例来说,注入到HomeController构造函数中的IServiceProvider是RequestServices,由于针对RequestServices的使用是在另一个后台线程中执行的,并且在使用的时候针对当前请求的处理已经结束(因为我们人为等待了100毫秒),自然就会出现上图所示的异常。

三、如何获取ApplicationServices

既然与请求绑定的RequestServices不能用,我们只能使用与应用绑定的ApplicationServices,那么后者如何得到呢?ASP.NET Core 3采用了基于IHost/IHostBuilder的承载方式,表示宿主的IHost接口具有如下所示的Services属性,它返回的正式我们所需的ApplicationServices。

public interface IHost : IDisposable
{
 Task StartAsync(CancellationToken cancellationToken = new CancellationToken());
 Task StopAsync(CancellationToken cancellationToken = new CancellationToken());

 IServiceProvider Services { get; }
}

对于我们演示的程序来说,我们可以采用如下的方式在HomeController的构造中注入IHost服务的方式间接地获得这个ApplicationServices对象。

public class HomeController: Controller
{
 private readonly IServiceProvider _applicationServices;
 public HomeController(IHost host)
 {
 _applicationServices = host.Services;
 }
 [HttpGet("/")]
 public IActionResult Index()
 {
 Task.Run(async() => {
 try
 {
 await Task.Delay(100);
 var foobar = _applicationServices.GetRequiredService<IFoobar>();
 }
 catch (Exception ex)
 {
 Console.WriteLine(ex.Message);
 Console.WriteLine(ex.StackTrace);
 }
 });
 return Ok();
 }
}

当我们采用如上的方式将RequestServices替换成ApplicationServices之后,我们的问题是否就解决了呢?在采用上面相同的方式进行测试之后,我们会发现服务端控制台上出现了如下所示的错误消息。

四、服务实例的生命周期

上面的问题是由我们试图利用一个代表“根容器”的IServiceProvider对象去解析一个生命周期模式为Scoped服务实例导致,具体的原因在《依赖注入[8]:服务实例的生命周期》已经讲得很清楚了。为了解决这个问题,我们应该根据ApplicationServices创建一个“服务范围”,并在该服务范围内提取我们所需的服务实例。为了确保服务实例能够被正常回收,我们还应该将代表服务范围的IServiceScope对象及时终结掉。如下所示的是正确的编程方式。

public class HomeController: Controller
{
 private readonly IServiceProvider _applicationServices;
 public HomeController(IHost host)
 {
 _applicationServices = host.Services;
 }
 [HttpGet("/")]
 public IActionResult Index()
 {
 Task.Run(async() => {
 await Task.Delay(100);
 using (var scope = _applicationServices.CreateScope())
 {
 var foobar = scope.ServiceProvider.GetRequiredService<IFoobar>();
 }
 });
 return Ok();
 }
}

五、统一的解决方案

之前我们将问题的解决方案落实在如何获取与当前应用具有相同生命周期的ApplicationServices上,所以我们采用注入IHost的方式得到这个ApplicationServices。如果采用传统的基于IWebHost/IWebHostBuilder的承载方式,IHost自然是获取不到了。但是我们是真的需要这个ApplicationServices对象吗?其实不是,我们真正需要的是利用它创建一个代表服务范围的IServiceScope对象,并在该范围内消费我们所需的服务实例。由于IServiceScope是通过IServiceScopeFactory创建的,所以我们只需要注入IServiceScopeFactory即可。

public class HomeController : Controller
{
 private readonly IServiceScopeFactory _serviceScopeFactory;

 public HomeController(IServiceScopeFactory serviceScopeFactory)
 {
 _serviceScopeFactory = serviceScopeFactory;
 }

 [HttpGet("/")]
 public IActionResult Index()
 {
 Task.Run(async () =>
 {
 await Task.Delay(100);
 using (var scope = _serviceScopeFactory.CreateScope())
 { 
 var foobar = scope.ServiceProvider.GetRequiredService<IFoobar>();
 }
 });
 return Ok();
 }
}

总结

以上所述是小编给大家介绍的ASP.NET Core 3框架揭秘之 异步线程无法使用IServiceProvider问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

相关文章

  • 浅谈ADO.NET数据库脚本

    浅谈ADO.NET数据库脚本

    最近跟着公司做了一个小项目,虽然不是很好,却也有点心得,在此和大家分享一下,希望高手能不吝赐教啊。。。还有很多得更加努力学习啊
    2015-06-06
  • webapi中如何使用依赖注入

    webapi中如何使用依赖注入

    本篇将要和大家分享的是webapi中如何使用依赖注入,依赖注入这个东西在接口中常用,实际工作中也用的比较频繁,因此这里分享两种在api中依赖注入的方式Ninject和Unity。下面跟着小编一起来看下吧
    2017-02-02
  • VS2005 水晶报表在时部署时遇到的问题

    VS2005 水晶报表在时部署时遇到的问题

    前几天在服务器上部署一个B/S程序的时候,程序中的水晶报表部分出了些问题,报错:Server Error in '/' Application.
    2010-02-02
  • asp.net 数据绑定的实例代码

    asp.net 数据绑定的实例代码

    这篇文章介绍了asp.net 数据绑定的实例代码,有需要的朋友可以参考一下
    2013-07-07
  • .NET微信开发之PC 端微信扫码注册和登录功能实现

    .NET微信开发之PC 端微信扫码注册和登录功能实现

    这篇文章主要介绍了.NET微信开发之PC 端微信扫码注册和登录功能实现的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-09-09
  • OpenCV 3.1.0+VS2015开发环境配置教程

    OpenCV 3.1.0+VS2015开发环境配置教程

    这篇文章主要为大家详细介绍了OpenCV 3.1.0+VS2015开发环境配置教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • .net core Api 部署到Linux的方法步骤

    .net core Api 部署到Linux的方法步骤

    这篇文章主要介绍了.net core Api 部署到Linux的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • AspNetCore中的中间件详解

    AspNetCore中的中间件详解

    ASP.NET Core处理请求的方式看做是一个管道,中间件是组装到应用程序管道中用来处理请求和响应的组件,这篇文章主要介绍了AspNetCore中的中间件详解,需要的朋友可以参考下
    2023-05-05
  • ASP.NET Ref和Out关键字区别分析

    ASP.NET Ref和Out关键字区别分析

    类型介绍 在几乎所有的OOP语言中,都存在2种类型的值。
    2009-02-02
  • 如何ASP.NET Core Razor中处理Ajax请求

    如何ASP.NET Core Razor中处理Ajax请求

    本篇技术文章主要给大家讲述了如何ASP.NET Core Razor中处理Ajax请求这方面的知识点,有兴趣的朋友参考下。
    2018-01-01

最新评论