Bien, esta será la última entrada por ahora de esta pequeña serie acerca de elementos con los que tenemos que tener cuidado cuando trabajemos con inyección de dependencias en .NET Core. En esta ocasión hablaremos sobre como funciona el ciclo de vida de nuestras dependencias y cuando se liberan los recursos de las mismas. Paria ello, vamos a empezar con la misma clase que hemos utilizado en esta serie, aunque ahora implementando IDisposable.
1 2 3 4 5 6 7 8 |
public class DependencyClass : IDisposable { public void Dispose() { Debug.WriteLine("Disposed..."); } } |
Bien, el sistema de DI de .NET Core nos asegura que la llamada a nuestro método Dispose se realizará una vez que el proveedor finalize su ámbito. Por el código que estoy viendo últimamente, mucha gente tiende a registrar ciertos componentes usando directamente el IServiceProvider que tenemos en nuestro método de configuración, que generalmente vivirá todo el tiempo que viva nuestra aplicación web. Por lo tanto, todas estas dependencias transient seguirán vivas en nuestras aplicaciones, haciendo que la memoria crezca ad infinitum. Vamos a ilustrar este caso con un sencillo ejemplo.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
services.AddTransient<DependencyClass>(); var provider = services.BuildServiceProvider(); var index = 0; while (index < 1000) { var solved = provider.GetRequiredService<DependencyClass>(); ++index; } |
Si cuando acaba la ejecución de este bucle revisáramos los miembros de nuestro provider veriamos un campo llamado _transientDisposables que mantienen una referencia a las 1000 dependencias que hemos resuelto del contenedor.
Por supuesto, esto no sucede en un ciclo normal de una aplicación ASP.NET Core puesto que las mismas se hacen en un scope que después es limpiado. Vendría ser algo como lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
services.AddTransient<DependencyClass>(); var provider = services.BuildServiceProvider(); var scopedFactory = provider.GetRequiredService<IServiceScopeFactory>(); var index = 0; while (index < 1000) { using (var scope = scopedFactory.CreateScope()) { var solved = scope.ServiceProvider.GetRequiredService<DependencyClass>(); } ++index; } |
El corolario de esta entrada es entonces.. ten cuidado de que proveedor haces la resolución de tus dependencias, ten cuidado que el mismo se libere y todos tus objetos disposables puedan a su vez liberar recursos. Revisa _transientDisposables para obtener posible información de fugas de memoria.
Saludos
Unai