本文翻譯自 "《Four ways to dispose IDisposables in ASP.NET Core》" ,由於水平有限,故無法保證翻譯完全正確,歡迎指出錯誤。謝謝! 介面是.NET中最常用的介面之一。當類型包含 "非托管資源的引用" .aspx),比如視窗句柄、文件或網路通信,可以實 ...
本文翻譯自《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;
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。