在ASP.NET Core中处置IDisposable的四种方法

.NET中最常用的实现接口之一是该IDisposable接口。IDisposable包含对非托管资源( .aspx)的引用(例如窗口句柄,文件或套接字)时,它们将实现垃圾收集器会自动释放托管(即.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语句

当典型的建议的方法消耗IDisposable在你的代码,是一个using块:

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

IDisposable以这种方式使用s可以确保正确处置它们,无论它们是否引发异常。如有必要,您也可以使用try-finally块:

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

在处理文件或流时,您经常会发现这种模式-您只需要短暂地完成这些工作,并且可以在同一范围内完成这些工作。不幸的是,有时这不适合您的情况,您可能需要从其他地方处置该对象。根据您的实际情况,您还可以使用许多其他选项。

注意:在可能的情况下,最佳做法是将对象放置在与创建对象相同的范围内。这将有助于防止内存泄漏和应用程序中意外的文件锁定,因为这些意外的对象意外处置。

在请求结束时处理-使用 RegisterForDispose

在ASP.NET Core或任何Web应用程序中工作时,将对象范围限定为单个请求是很常见的。也就是说,您创建的用于处理要在请求完成时处理的请求的任何内容。

有很多方法可以做到这一点。最常见的方法是利用我稍后会谈到的DI容器,但是有时这是不可能的,您需要用自己的代码创建一次性容器。

如果您正在手动创建的实例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();
    }
}

在此示例中,我正在创建的Disposable构造函数期间HomeController,然后在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需要处置我们的对象对我们的关怀!

警告:我将实例注册在操作方法中,而不是在构造方法中,因为它HttpContext可能null在构造函数中!

RegisterForDisposenew在代码中建立服务时很有用但是,鉴于只有使用非托管资源的类才需要使用Dispose,因此您可能会发现,通常情况下,您的IDisposable类封装在使用DI容器注册的服务中。

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

自动处理服务-利用内置的DI容器

ASP.NET Core带有一个简单的内置DI容器,您可以将其注册为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 作为暂时的
  • ScopedCreatedByFactory 范围内,使用lambda函数作为工厂
  • SingletonCreatedByContainer 作为一个单身人士
  • SingletonAddedManually通过传入对象特定实例作为单例
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,因此DI容器将根据需要创建/注入实例:

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 从来没有处置过,因为它是提供给特定实例的!

请注意,仅处理由DI容器创建的对象的行为适用于ASP.NET Core 1.1及更高版本。在ASP.NET Core 1.0中,处理了在容器中注册的所有对象。

让容器IDisposable为您处理s显然很方便,特别是因为无论如何您可能都在用它注册服务!这里唯一明显的漏洞是您是否需要处置自己创建的对象。正如我最初所说,如果可能的话,您应该支持一个using声明,但这并不总是可能的。幸运的是,ASP.NET Core提供了应用程序生命周期的挂钩,因此您可以在应用程序关闭时进行一些清理。

在应用程序结束时处理-挂接到IApplicationLifetime事件

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

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

您可以将其注入您的Startup班级(或其他地方)并注册到所需的事件。扩展前面的示例,我们可以将实例IApplicationLifetime和SingletonSingletonAddedManually实例都注入Startup.csConfigure方法中

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语句,或让DI容器为您处理对象。对于无法实现的情况,ASP.NET Core提供了两种可以连接的机制:RegisterForDisposeIApplicationLifetime

posted @ 2021-02-01 17:41  竹林听雨行  阅读(345)  评论(0编辑  收藏  举报