ASP.NET Core 释放 IDisposable 对象的四种方法
本文翻译自《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方法中注册实例,而不是构造方法,是因为在构造函数中
HttpContext
为null
!
在您的代码中,RegisterForDispose
对处理创建的服务很有用。但是鉴于Dispose模式仅适用于使用非托管资源的类,您可能会发现,通常情况下,您的IDisposable
类被封装在使用依赖容器注册的服务中。
正如 Mark Rendle 指出的那样,
Controller
本身也将在请求结束时释放,因此您可以使用该机制来处理您创建的任何对象。
## 自动释放服务 - 利用内置依赖容器
ASP.NET Core附带一个简单的内置依赖容器,您可以使用“Transient”,“Scoped”或“Singleton”注册您的服务。你可以在这里了解更多,所以我假设您已经知道如何使用它来注册您的服务。
请注意,本文仅讨论内置容器 - 第三方容器可能有其它关于自动处理服务的规则。
内置容器可以填充任何服务创建的依赖项,它将实现了IDisposable
接口的对象,将在适当的时候由容器释放。因此Transient
,Scoped
实例将在请求结束时(或更准确地说,在范围结束时),Singleton
实例在应用程序被关闭释放,并且ServiceProvider
自身也会被释放。
这意味着只要您不提供具体的实例,提供者将释放您注册的任何服务。例如,我将创建一些可释放类:
public class TransientCreatedByContainer: MyDisposable { }
public class ScopedCreatedByFactory : MyDisposable { }
public class SingletonCreatedByContainer: MyDisposable {}
public class SingletonAddedManually: MyDisposable {}
在Startup.ConfigureServices
方法以不同的方式注册它们。我将这样注册:
TransientCreatedByContainer
- transientScopedCreatedByFactory
- scoped,使用lambda函数作为工厂SingletonCreatedByContainer
- singletonSingletonAddedManually
- 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.cs 的Configure
方法中注入 IApplicationLifetime
和SingletonAddedManually
实例的单例:
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
并将其释放。该帮助方法被注册到类型是CancellationToken
的ApplicationStopping
属性中,当关闭应用程序时,该方法被触发。
如果我们再次运行应用程序,通过此额外的注册,您可以看到该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 对象的四种方法的更多相关文章
- C#调用接口注意要点 socket,模拟服务器、客户端通信 在ASP.NET Core中构建路由的5种方法
C#调用接口注意要点 在用C#调用接口的时候,遇到需要通过调用登录接口才能调用其他的接口,因为在其他的接口需要在登录的状态下保存Cookie值才能有权限调用, 所以首先需要通过调用登录接口来保存c ...
- ASP.Net Core中处理异常的几种方法
本文将介绍在ASP.Net Core中处理异常的几种方法 1使用开发人员异常页面(The developer exception page) 2配置HTTP错误代码页 Configuring stat ...
- 在ASP.NET Core中构建路由的5种方法
原文链接 :https://stormpath.com/blog/routing-in-asp-net-core 在ASP.NET Core中构建路由的5种方法 原文链接 :https://storm ...
- 在Action类中获得HttpServletResponse对象的四种方法
在struts1.xAction类的execute方法中,有四个参数,其中两个就是response和request.而在Struts2中,并没有任何参数,因此,就不能简单地从execute方法获得Ht ...
- Java遍历Map对象的四种方法
在java中遍历Map有不少的方法.我们看一下最常用的方法及其优缺点. 既然java中的所有map都实现了Map接口,以下方法适用于任何map实现(HashMap, TreeMap, LinkedHa ...
- JavaSE-反射-获取类或者对象的四种方法
1.使用Class类的静态方法Class.forName("xxxx"); 新建一个要想要获取的类 package org.burning.sport.javase.classlo ...
- ASP.NET 使用 Dispose 释放资源的四种方法
Dispose 和 Finalize 是运行的 .NET 和 .NET Core 应用程序释放占用的资源的两种方法.通常,如果应用程序中有非托管资源,应该显式地释放这些资源占用的资源. 由于 Fina ...
- ASP.NET Core 四种释放 IDisposable 对象的方法
本文翻译自<Four ways to dispose IDisposables in ASP.NET Core>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! IDispos ...
- Asp.Net Core WebApi学习笔记(四)-- Middleware
Asp.Net Core WebApi学习笔记(四)-- Middleware 本文记录了Asp.Net管道模型和Asp.Net Core的Middleware模型的对比,并在上一篇的基础上增加Mid ...
随机推荐
- Python shelve
shelve模块只有一个open函数,返回类似字典的对象,可读可写; key必须为字符串,而值可以是python所支持的数据类型. import shelve f = shelve.open('SHE ...
- 关于<checkbox>checked默认问题
这个问题困扰我有一段时间了,今天终于解决了. 接手现在的项目半个月了,我看之前的人写的<checkbox checked="false" />一直没有生效,但是需求上没 ...
- 洛谷P4438 [HNOI/AHOI2018]道路(dp)
题意 题目链接 Sol 每当出题人想起他出的HNOI 2018 Day2T3,他都会激动的拍打着轮椅 读题比做题用时长系列... \(f[i][a][b]\)表示从根到\(i\)的路径上,有\(a\) ...
- python之网络通信协议
TCP/IP五层协议和OSI的七层协议: TCP和UDP的区别: Tcp协议:面向连接,数据可靠,传输效率低,面向字节流 Udp协议:面向无连接,数据不可靠,传输效率高,面向报文
- loadrunner 11 安装与使用
注:以下链接均为转载,详细内容请查看原文. 安装教程: https://blog.csdn.net/u010731693/article/details/78986840 使用教程: https:// ...
- 异步陷阱之IO篇
很多教程和资料都强调流畅的用户体验需要异步来辅助,核心思想就是保证用户前端的交互永远有最高的优先级,让一切费时的逻辑通通放到后台,等到诸事完备,通知一下前端给个提示或者继续下一步.随着.NET发展,a ...
- (网页)20个JS 小技巧超级实用
转自CSDN: 1. 将彻底屏蔽鼠标右键 oncontextmenu=”window.event.returnValue=false”< table border oncontextmenu=r ...
- LeetCode题解之Binary Tree Pruning
1.题目描述 2.问题分析 使用递归 3.代码 TreeNode* pruneTree(TreeNode* root) { if (root == NULL) return NULL; prun(ro ...
- tkinter做一个简单的登陆页面(十六)
做一个简单的登陆页面 import tkinter wuya = tkinter.Tk() wuya.title("wuya") wuya.geometry("900x3 ...
- MVC| NuGet安装相关工具包
----------------------------------------------Ninject----------------------------------------------- ...