本文翻译自《Four ways to dispose IDisposables in ASP.NET Core》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!


IDisposable 接口是.NET中最常用的接口之一。当类型包含非托管资源的引用,比如窗口句柄、文件或网络通信,可以实现IDisposable接口。垃圾收集器自动释放托管(即.NET)对象的内存,但不知道如何处理非托管资源。通过实现IDisposable接口,您可以在类被释放时正确地清理这些资源。

这篇文章介绍了在ASP.NET Core应用程序中可以用于处理释放资源的一些方法,特别是在使用内置的依赖注入容器时。

为了达到这篇文章的目的,我在示例中使用下面实现了IDisposable接口的类。为了达到我们演示的目的,只需要将日志输出到控制台,而不需要做任何实际的清理工作。

public class MyDisposable : IDisposable
{
public MyDisposable()
{
Console.WriteLine("+ {0} was created", this.GetType().Name);
} public void Dispose()
{
Console.WriteLine("- {0} was disposed!", this.GetType().Name);
}
}

现在来看看我们的方案。


最简单的方法 - using语法

在代码中使用using语句块释放一个IDisposable对象是一种最普通的方法:

using(var myObject = new MyDisposable())
{
// myObject.DoSomething();
}

使用using语句块的方式,无论是否抛出异常,都能确保Dispose方法可以正常的执行。如果需要,您也可以使用try- finally语句块的方式:

MyDisposable myObject = null;
try
{
myObject = new MyDisposable();
// myObject.DoSomething();
}
finally
{
myObject?.Dispose();
}

您会发现通常在使用文件或流(在短暂的某一范围内)等会用此模式。不幸的是,有时不一定符合这种情况,您可能需要在其它的地方释放该对象。根据您的真实情况,还可以使用一些其它的方式。

注意: 只要有可能,最好的做法就是将它们在创建的使用范围内释放。这将有助于防止应用程序中的内存泄漏和意外的文件锁,或者对象意外地未释放。



## 在请求结束时释放 - 使用`RegisterForDispose`

当您在ASP.NET Core或任何Web应用程序工作时,将对象的使用范围限定为单个请求是非常常见的。也就是说,任何您在请求时创建的对像,在请求完成时释放该对象。

有很多方法可以做到这一点。最常见的方法是在利用依赖容器(我马上就会讲到),但有时候不可能,因为您可能需要在代码中手动创建IDisposable对象。

如果您手动创建一个IDisposable实例,则可以将该实例注册到HttpContext中,以便在请求结束时,该实例被自动释放。只需将实例传递给HttpContext.Response.RegisterForDispose方法:

public class HomeController : Controller
{
readonly Disposable _disposable; public HomeController()
{
_disposable = new RegisteredForDispose();
} public IActionResult Index()
{
// register the instance so that it is disposed when request ends
HttpContext.Response.RegisterForDispose(_disposable);
Console.Writeline("Running index...");
return View();
}
}

在这个例子中,我在HomeController的构造函数中创建Disposable对象,然后在action方法中注册它。这种设计有点做作,但至少展示了这种机制。

如果执行此action方法,您将看到以下内容:

$ dotnet run
Hosting environment: Development
Content root path: C:\Users\Sock\Repos\RegisterForDispose
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
+ MyDisposable was created
Running index...
- MyDisposable was disposed!

HttpContext负责为我们释放我们创建的对象!

警告: 我在action方法中注册实例,而不是构造方法,是因为在构造函数中HttpContextnull

在您的代码中,RegisterForDispose对处理创建的服务很有用。但是鉴于Dispose模式仅适用于使用非托管资源的类,您可能会发现,通常情况下,您的IDisposable类被封装在使用依赖容器注册的服务中。

正如 Mark Rendle 指出的那样,Controller 本身也将在请求结束时释放,因此您可以使用该机制来处理您创建的任何对象。



## 自动释放服务 - 利用内置依赖容器

ASP.NET Core附带一个简单的内置依赖容器,您可以使用“Transient”,“Scoped”或“Singleton”注册您的服务。你可以在这里了解更多,所以我假设您已经知道如何使用它来注册您的服务。

请注意,本文仅讨论内置容器 - 第三方容器可能有其它关于自动处理服务的规则。

内置容器可以填充任何服务创建的依赖项,它将实现了IDisposable接口的对象,将在适当的时候由容器释放。因此TransientScoped实例将在请求结束时(或更准确地说,在范围结束时),Singleton实例在应用程序被关闭释放,并且ServiceProvider自身也会被释放。

这意味着只要您不提供具体的实例,提供者将释放您注册的任何服务。例如,我将创建一些可释放类:

public class TransientCreatedByContainer: MyDisposable { }
public class ScopedCreatedByFactory : MyDisposable { }
public class SingletonCreatedByContainer: MyDisposable {}
public class SingletonAddedManually: MyDisposable {}

Startup.ConfigureServices方法以不同的方式注册它们。我将这样注册:

  • TransientCreatedByContainer - transient
  • ScopedCreatedByFactory - scoped,使用lambda函数作为工厂
  • SingletonCreatedByContainer - singleton
  • SingletonAddedManually - singleton,传递具体的实例对象
public void ConfigureServices(IServiceCollection services)
{
// other services // these will be disposed
services.AddTransient<TransientCreatedByContainer>();
services.AddScoped(ctx => new ScopedCreatedByFactory());
services.AddSingleton<SingletonCreatedByContainer>(); // this one won't be disposed
services.AddSingleton(new SingletonAddedManually());
}

最后,我将在HomeController中依次传每个实例,因此依赖容器将根据需要创建/注入实例:

public class HomeController : Controller
{
public HomeController(
TransientCreatedByContainer transient,
ScopedCreatedByFactory scoped,
SingletonCreatedByContainer createdByContainer,
SingletonAddedManually manually)
{ } public IActionResult Index()
{
return View();
}
}

当我运行应用程序,点击主页,然后停止应用程序,我将得到以下输出:

$ dotnet run
+ SingletonAddedManually was created
Content root path: C:\Users\Sock\Repos\RegisterForDispose
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
+ TransientCreatedByContainer was created
+ ScopedCreatedByFactory was created
+ SingletonCreatedByContainer was created
- TransientCreatedByContainer was disposed!
- ScopedCreatedByFactory was disposed!
Application is shutting down...
- SingletonCreatedByContainer was disposed!

这里有几件事要注意:

  • SingletonAddedManually 是在Web主机完成设置之前创建的,因此,在日志开始之前,它将写入控制台
  • SingletonCreatedByContainer 在我们关闭服务之后被释放
  • SingletonAddedManually 从来没有释放,因为我们提供了一个具体的实例!

请注意,由依赖容器创建的对象被释放的行为只适用于ASP.NET Core 1.1及更高版本。在ASP.NET Core 1.0中,所有*容器注册的对象都会被释放。*

让容器帮您处理IDisposable对象显然很方便,特别是您可能已经在注册您的服务!这里唯一的需要注意的是您需要释放您自己创建的对象。正如我刚才所说,如果可能,您应该尽量使用using语法,但这并不总是可能的。幸运的是,ASP.NET Core 应用程序的生命周期提供了机制,所以在应用程序关闭时可以进行一些清理。



## 应用程序结束时释放 - 利用 `IApplicationLifetime` 事件

ASP.NET Core公开了一个称为 IApplicationLifetime 的接口,可用于在应用程序启动或关闭时执行代码:

public interface IApplicationLifetime
{
CancellationToken ApplicationStarted { get; }
CancellationToken ApplicationStopping { get; }
CancellationToken ApplicationStopped { get; }
void StopApplication();
}

您可以将其注入您的Startup 类(或其它类),并注册您需要的事件。扩展前面的例子,我们在 Startup.csConfigure方法中注入 IApplicationLifetimeSingletonAddedManually实例的单例:

public void Configure(
IApplicationBuilder app,
IApplicationLifetime applicationLifetime,
SingletonAddedManually toDispose)
{
applicationLifetime.ApplicationStopping.Register(OnShutdown, toDispose); // configure middleware etc
} private void OnShutdown(object toDispose)
{
((IDisposable)toDispose).Dispose();
}

我创建了一个简单的帮助方法,传入SingletonAddedManually的实例,将其转换为IDisposable并将其释放。该帮助方法被注册到类型是CancellationTokenApplicationStopping属性中,当关闭应用程序时,该方法被触发。

如果我们再次运行应用程序,通过此额外的注册,您可以看到该SingletonAddedManually实例现在已被释放,就在应用程序关闭之后触发。

$ dotnet run
+ SingletonAddedManually was created
Content root path: C:\Users\Sock\Repos\RegisterForDispose
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
+ TransientCreatedByContainer was created
+ ScopedCreatedByFactory was created
+ SingletonCreatedByContainer was created
- TransientCreatedByContainer was disposed!
- ScopedCreatedByFactory was disposed!
Application is shutting down...
- SingletonAddedManually was disposed!
- SingletonCreatedByContainer was disposed!



## 概要

您有四种不同的方法来处理您的IDisposable对象。只要有可能,您应该使用using语法,或者让依赖容器为您释放对象。对于不可能的情况,ASP.NET Core提供了两种可以挂接的机制:RegisterForDispose和IApplicationLifetime。


转载请注明出处,原文链接:http://www.cnblogs.com/tdfblog/p/four-ways-to-dispose-idisposables-in-asp-net-core.html


ASP.NET Core 四种释放 IDisposable 对象的方法的更多相关文章

  1. ASP.NET Core 四种方式绑定枚举值

    前言 本节我们来讲讲在ASP.NET Core MVC又为我们提供了哪些方便,之前我们探讨过在ASP.NET MVC中下拉框绑定方式,这节我们来再来重点看看枚举绑定的方式,充分实现你所能想到的场景,满 ...

  2. ASP.NET MVC 四种Controller向View传值方法

    控制器: // Get: Data public ActionResult Index() { //ViewData 方式 ViewData["UserName"] = " ...

  3. Java中四种遍历Map对象的方法

    方法一:在for-each循环中使用entry来遍历,通过Map.entrySet遍历key和value,这是最常见的并且在大多数情况下也是最可取的遍历方式.在键值都需要时使用. Map<Int ...

  4. ASP.NET CORE 中使用AutoMapper进行对象映射

    ASP.NET CORE 中使用AutoMapper进行对象映射 1.什么是AutoMapper? AutoMapper是基于对象到对象约定的映射工具,常用于(但并不仅限制于)把复杂的对象模型转为DT ...

  5. 几种创建XMLHttpRequest对象的方法

    XMLHttpRequest对象,也就是Ajax交互的核心对象. 这里列举三种创建Ajax对象的方法. 第一种: <!DOCTYPE html> <html> <head ...

  6. http协议里定义的四种常见数据的post方法

    原文 https://blog.csdn.net/charlene0824/article/details/51199292 关于http协议里定义的四种常见数据的post方法,分别是: applic ...

  7. ASP.NET Core 释放 IDisposable 对象的四种方法

    本文翻译自<Four ways to dispose IDisposables in ASP.NET Core>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! IDispos ...

  8. asp.net core 四 IOC&DI Autofac

    其实关于IOC,DI已经有了很多的文章,但是自己在使用中还是有很多困惑,而且相信自己使用下,印象还是会比较深刻的 关于这段时间一直在学习.net core,但是这篇文章是比较重要的,也是我自己觉得学习 ...

  9. ASP.NET Core DI 手动获取注入对象

    ASP.NET Core DI 一般使用构造函数注入获取对象,比如在ConfigureServices配置注入后,通过下面方式获取: private IValueService _valueServi ...

随机推荐

  1. jQuery使用记录

    jQuery方法获得的集合为类数组对象,可以用一些数组的方法.集合里面的元素为DOM元素,不是jQuery对象. jQuery设置css3属性的兼容性写法: $(element).css({ &quo ...

  2. struts2.1.6教程六、使用标签

    1.基础表单标签 准备工作:建立struts2tag项目,搭建好struts2的开发环境.在html我们常用的基础表单标签主要有文本域.密码域.提交.重置四种.它们在strust2中可以通过标签来生成 ...

  3. 开涛spring3(3.4) - DI 之 3.4 Bean的作用域

    3.4  Bean的作用域 什么是作用域呢?即“scope”,在面向对象程序设计中一般指对象或变量之间的可见范围.而在Spring容器中是指其创建的Bean对象相对于其他Bean对象的请求可见范围. ...

  4. rowid去重(删除表的重复记录)

    -- 构造测试环境SQL> create table andy(id int,name varchar2(10));Table created.SQL>insert into andy v ...

  5. APP为什么需要着陆页

    小编APP运营团队一直认为,虽然大多数的用户会在App Store或安卓应用商店中搜寻我们的应用,但也有许多用户会通过传统的PC端搜索来寻找答案.而且在APP营销中,为了更好的将用户转换为下载或购买, ...

  6. 如何在不使用系统函数的情况下实现PHP中数组系统函数的功能

    PHP中为我们提供了各种各样的系统函数来实现我们需要的各种功能,那么,在不使用系统函数的情况下我们要怎样来实现这些功能呢?以下就是几种系统函数的实现方式. 首先,我们来定义一个数组: $arr= ar ...

  7. IE 不兼容 js indexOf 函数

      在使用 js 判断数组中是否存储该元素,我们会用到 indexOf 函数.而在 IE 上 indexOf 函数 无法兼容,通过以下方法解决,仅以文章记录一下 if (!Array.prototyp ...

  8. 设置Intellij IDEA 提示出未保存的*星号

    IDEA实乃是java开发的神器,然而从eclipse或者MyEclipse转过来的开发每写完一行代码总是习惯的按下ctrl+s,然而IDEA代码编辑器的上方并没有提示未保存的*星号,提示星星,所以觉 ...

  9. JS 使用 splice() 对数组去重

    一 问题 有如下 js 数组 connect_clients,需要在去掉所有元素中 user_id, goods_id 这两者的值都相同的元素. [ { id: 'eff040fb-92bc-4f24 ...

  10. {网络编程}和{多线程}应用:基于TCP协议【实现多个客户端发送文件给一个服务器端】--练习

    要求: 实现多个客户端发送文件给一个服务器端 提示:多个人创建客户端发送文件,服务端循环接收socket,从socket中获取文件 说明:这里我们只要建立一个服务端就可以了,然后让多台电脑使用客户端给 ...