ASP.NET Core Procedimientos recomendados de rendimiento
Por Mike Rousos
En este artículo se proporcionan directrices para los procedimientos recomendados de rendimiento ASP.NET Core.
Almacenar en caché de forma agresiva
El almacenamiento en caché se describe en varias partes de este documento. Para obtener más información, vea Almacenamiento en caché de respuestas en ASP.NET Core.
Información sobre las rutas de acceso de código de acceso
En este documento, una ruta de acceso de código frecuente se define como una ruta de acceso de código a la que se llama con frecuencia y donde se produce gran parte del tiempo de ejecución. Las rutas de acceso de código activo suelen limitar el escalado horizontal y el rendimiento de las aplicaciones, y se analizan en varias partes de este documento.
Evitar el bloqueo de llamadas
ASP.NET Core aplicaciones deben diseñarse para procesar muchas solicitudes simultáneamente. Las API asincrónicas permiten que un pequeño grupo de subprocesos controle miles de solicitudes simultáneas sin esperar a las llamadas de bloqueo. En lugar de esperar a que se complete una tarea sincrónica de larga duración, el subproceso puede trabajar en otra solicitud.
Un problema de rendimiento común en ASP.NET Core aplicaciones es bloquear llamadas que podrían ser asincrónicas. Muchas llamadas de bloqueo sincrónicas conducen a la escasez del grupo de subprocesos y a tiempos de respuesta degradados.
No:
- Bloquee la ejecución asincrónica mediante la llamada a Task.Wait o Task.Result.
- Adquiera bloqueos en rutas de acceso de código comunes. ASP.NET Core aplicaciones son de mayor rendimiento cuando se diseñó para ejecutar código en paralelo.
- Llame a Task.Run y espere inmediatamente. ASP.NET Core ya ejecuta código de aplicación en los subprocesos normales del grupo de subprocesos, por lo que la llamada a Task.Run solo da lugar a la programación adicional innecesaria del grupo de subprocesos. Aunque el código programado bloquee un subproceso, Task.Run no lo impide.
Sí:
- Haga que las rutas de acceso de código de acceso de acceso sea asincrónicas.
- Llame a las API de acceso a datos, E/S y operaciones de ejecución larga de forma asincrónica si hay una API asincrónica disponible. No use Task.Run para que una API sincrónica sea asincrónica.
- Hacer que las acciones de Razor controlador o página se realicen de forma asincrónica. Toda la pila de llamadas es asincrónica para beneficiarse de los patrones async/await.
Un generador de perfiles, como PerfView, se puede usar para buscar subprocesos que se agregan con frecuencia al grupo de subprocesos. El evento Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start indica que se agregó un subproceso al grupo de subprocesos.
Devolver colecciones grandes en varias páginas más pequeñas
Una página web no debe cargar grandes cantidades de datos a la vez. Al devolver una colección de objetos, considere si podría provocar problemas de rendimiento. Determine si el diseño podría producir los siguientes resultados deficientes:
- OutOfMemoryException o consumo elevado de memoria
- Falta de vida del grupo de subprocesos (vea los comentarios siguientes en IAsyncEnumerable<T> )
- Tiempos de respuesta lentos
- Recolección de elementos no utilizados frecuente
Agregue paginación para mitigar los escenarios anteriores. Mediante el uso de parámetros de tamaño de página e índice de página, los desarrolladores deben favorecer el diseño de devolver un resultado parcial. Cuando se requiere un resultado exhaustivo, se debe usar la paginación para rellenar de forma asincrónica lotes de resultados para evitar el bloqueo de los recursos del servidor.
Para obtener más información sobre la paginación y la limitación del número de registros devueltos, vea:
Devolver IEnumerable<T> o IAsyncEnumerable<T>
La IEnumerable<T> devolución de una acción da como resultado una iteración de colección sincrónica por parte del serializador. El resultado es el bloqueo de llamadas y una posibilidad de colapso del grupo de subprocesos. Para evitar la enumeración sincrónica, use ToListAsync antes de devolver el enumerable.
A partir ASP.NET Core 3.0, se puede usar como alternativa IAsyncEnumerable<T> a que enumera de forma IEnumerable<T> asincrónica. Para obtener más información, vea Tipos de valor devuelto de acción de controlador.
Minimizar las asignaciones de objetos grandes
El recolector de elementos no utilizados de .NET Core administra automáticamente la asignación y la liberación de memoria en ASP.NET Core aplicaciones. Por lo general, la recolección automática de elementos no utilizados significa que los desarrolladores no tienen que preocuparse por cómo o cuándo se libera memoria. Sin embargo, la limpieza de objetos sin referencia lleva tiempo de CPU, por lo que los desarrolladores deben minimizar la asignación de objetos en rutas de acceso de código activa. La recolección de elementos no utilizados es especialmente costosa en objetos grandes (> 85 K bytes). Los objetos grandes se almacenan en el montón de objetos grandes y requieren una recolección completa de elementos no utilizados (generación 2) para limpiar. A diferencia de las colecciones de generación 0 y 1, una colección de generación 2 requiere una suspensión temporal de la ejecución de la aplicación. La asignación y la desatribución frecuentes de objetos grandes pueden provocar un rendimiento incoherente.
Recomendaciones:
- Considere la posibilidad de almacenar en caché objetos grandes que se usan con frecuencia. El almacenamiento en caché de objetos grandes evita asignaciones costosas.
- Realice búferes de grupo mediante ArrayPool <T> para almacenar matrices de gran tamaño.
- No asigne muchos objetos grandes de corta duración en las rutas de acceso de código de acceso.
Los problemas de memoria, como los anteriores, se pueden diagnosticar revisando las estadísticas de recolección de elementos no utilizados (GC) en PerfView y examinando:
- Tiempo de pausa de recolección de elementos no utilizados.
- Qué porcentaje del tiempo de procesador se dedica a la recolección de elementos no utilizados.
- Cuántas recolecciones de elementos no utilizados son de las generaciones 0, 1 y 2.
Para obtener más información, vea Recolección de elementos no utilizados y Rendimiento.
Optimización del acceso a datos y la E/S
Las interacciones con un almacén de datos y otros servicios remotos suelen ser las partes más lentas de una aplicación ASP.NET Core datos. Leer y escribir datos de forma eficaz es fundamental para un buen rendimiento.
Recomendaciones:
- Llame a todas las API de acceso a datos de forma asincrónica.
- No recupere más datos de los necesarios. Escriba consultas para devolver solo los datos necesarios para la solicitud HTTP actual.
- Considere la posibilidad de almacenar en caché los datos a los que se accede con frecuencia recuperados de una base de datos o un servicio remoto si los datos ligeramente desatendados son aceptables. En función del escenario, use MemoryCache o DistributedCache. Para obtener más información, vea Almacenamiento en caché de respuestas en ASP.NET Core.
- Minimice los recorridos de ida y vuelta de red. El objetivo es recuperar los datos necesarios en una sola llamada en lugar de en varias llamadas.
- Use consultas sin seguimiento en Entity Framework Core al acceder a los datos con fines de solo lectura. EF Core pueden devolver los resultados de las consultas sin seguimiento de forma más eficaz.
- Filtre y agregue consultas LINQ (con instrucciones , o , por ejemplo) para que la base de datos realice
.Where.Selectel.Sumfiltrado. - Tenga en cuenta que EF Core algunos operadores de consulta en el cliente, lo que puede dar lugar a una ejecución de consultas ineficaz. Para obtener más información, vea Problemas de rendimiento de evaluación de cliente.
- No use consultas de proyección en colecciones, lo que puede dar lugar a la ejecución de "N + 1" SQL consultas. Para obtener más información, vea Optimización de subconscuencias correlacionadas.
Consulte EF High Performance (Alto rendimiento de EF) para ver los enfoques que pueden mejorar el rendimiento en aplicaciones a gran escala:
Se recomienda medir el impacto de los enfoques de alto rendimiento anteriores antes de confirmar la base de código. Es posible que la complejidad adicional de las consultas compiladas no justifique la mejora del rendimiento.
Los problemas de consulta se pueden detectar revisando el tiempo dedicado a acceder a los datos con application Ideas o con herramientas de generación de perfiles. La mayoría de las bases de datos también hacen que las estadísticas estén disponibles con respecto a las consultas ejecutadas con frecuencia.
Agrupación de conexiones HTTP con HttpClientFactory
Aunque HttpClient implementa la IDisposable interfaz, está diseñada para su reutilización. Las HttpClient instancias cerradas dejan los sockets abiertos en TIME_WAIT el estado durante un breve período de tiempo. Si se usa con frecuencia una ruta de acceso de código que crea y elimina HttpClient objetos, la aplicación puede agotar los sockets disponibles. HttpClientFactory se introdujo en ASP.NET Core 2.1 como solución a este problema. Controla la agrupación de conexiones HTTP para optimizar el rendimiento y la confiabilidad.
Recomendaciones:
- No cree ni deseche
HttpClientlas instancias directamente. - Use HttpClientFactory para recuperar
HttpClientinstancias. Para obtener más información, consulte el artículo sobre el uso de HttpClientFactory para implementar solicitudes HTTP resistentes.
Mantener las rutas de acceso de código comunes rápidas
Quiere que todo el código sea rápido. Las rutas de acceso de código llamadas con frecuencia son las más críticas para optimizar. Entre ellas se incluyen las siguientes:
- Los componentes de middleware de la canalización de procesamiento de solicitudes de la aplicación, especialmente el middleware, se ejecutan al principio de la canalización. Estos componentes tienen un gran impacto en el rendimiento.
- Código que se ejecuta para cada solicitud o varias veces por solicitud. Por ejemplo, registro personalizado, controladores de autorización o inicialización de servicios transitorios.
Recomendaciones:
- No use componentes de middleware personalizados con tareas de ejecución larga.
- Use herramientas de generación de perfiles de rendimiento, como Visual Studio Herramientas de diagnóstico o PerfView), para identificar las rutas de acceso de código de acceso rápido.
Completar tareas de ejecución larga fuera de las solicitudes HTTP
La mayoría de las solicitudes a ASP.NET Core aplicación pueden controlarse mediante un controlador o modelo de página que llama a los servicios necesarios y devuelve una respuesta HTTP. Para algunas solicitudes que implican tareas de ejecución larga, es mejor hacer que todo el proceso de solicitud-respuesta sea asincrónico.
Recomendaciones:
- No espere a que las tareas de ejecución larga se completen como parte del procesamiento de solicitudes HTTP normales.
- Considere la posibilidad de controlar solicitudes de ejecución larga con servicios en segundo plano o fuera de proceso con una función de Azure. Completar el trabajo fuera de proceso es especialmente beneficioso para las tareas que consumen mucha CPU.
- Use opciones de comunicación en tiempo real, como SignalR , para comunicarse con los clientes de forma asincrónica.
Minify client assets (Minify client assets) (Minify client
ASP.NET Core aplicaciones con servidores front-ends complejos a menudo sirven muchos archivos de JavaScript, CSS o imagen. El rendimiento de las solicitudes de carga iniciales se puede mejorar mediante:
- Agrupación, que combina varios archivos en uno.
- Minifying, que reduce el tamaño de los archivos mediante la eliminación de espacios en blanco y comentarios.
Recomendaciones:
- Use las directrices deunión y minificación , que menciona las herramientas compatibles y muestra cómo usar la etiqueta de ASP.NET Core para controlar los
environmentDevelopmentProductionentornos y . - Considere otras herramientas de terceros, como Webpack,para la administración compleja de recursos de cliente.
Comprimir respuestas
La reducción del tamaño de la respuesta suele aumentar la capacidad de respuesta de una aplicación, a menudo drásticamente. Una manera de reducir los tamaños de carga es comprimir las respuestas de una aplicación. Para obtener más información, vea Compresión de respuesta.
Uso de la versión ASP.NET Core más reciente
Cada nueva versión de ASP.NET Core incluye mejoras de rendimiento. Las optimizaciones en .NET Core ASP.NET Core significan que las versiones más recientes suelen superar a las versiones anteriores. Por ejemplo, .NET Core 2.1 agregó compatibilidad con expresiones regulares compiladas y se benefició de Span <T> . ASP.NET Core 2.2 agregó compatibilidad con HTTP/2. ASP.NET Core 3.0 agrega muchas mejoras que reducen el uso de memoria y mejoran el rendimiento. Si el rendimiento es una prioridad, considere la posibilidad de actualizar a la versión actual de ASP.NET Core.
Minimizar excepciones
Las excepciones deben ser poco frecuentes. Iniciar y detectar excepciones es lento en relación con otros patrones de flujo de código. Debido a esto, no se deben usar excepciones para controlar el flujo de programa normal.
Recomendaciones:
- No use iniciar o detectar excepciones como medio de flujo de programa normal, especialmente en las rutas de acceso de código de acceso rápido.
- Incluya lógica en la aplicación para detectar y controlar las condiciones que provocarían una excepción.
- Iniciar o detectar excepciones para condiciones inusuales o inesperadas.
Las herramientas de diagnóstico de aplicaciones, como Application Ideas, pueden ayudar a identificar excepciones comunes en una aplicación que pueden afectar al rendimiento.
Rendimiento y confiabilidad
En las secciones siguientes se proporcionan sugerencias de rendimiento y soluciones y problemas conocidos de confiabilidad.
Evitar la lectura o escritura sincrónica en el cuerpo de HttpRequest/HttpResponse
Todas las operaciones de E/S ASP.NET Core son asincrónicas. Los servidores implementan Stream la interfaz , que tiene sobrecargas sincrónicas y asincrónicas. Se deben preferir los asincrónicos para evitar el bloqueo de subprocesos del grupo de subprocesos. El bloqueo de subprocesos puede provocar el desenlazamiento del grupo de subprocesos.
No haga lo siguiente: En el ejemplo siguiente se usa ReadToEnd . Bloquea el subproceso actual para esperar el resultado. Este es un ejemplo de sincronización a través de async.
public class BadStreamReaderController : Controller
{
[HttpGet("/contoso")]
public ActionResult<ContosoData> Get()
{
var json = new StreamReader(Request.Body).ReadToEnd();
return JsonSerializer.Deserialize<ContosoData>(json);
}
}
En el código anterior, lee Get sincrónicamente todo el cuerpo de la solicitud HTTP en la memoria. Si el cliente se carga lentamente, la aplicación está realizando la sincronización a través de una asincrónica. La aplicación se sincroniza a través de async porque Kestrel NO admite lecturas sincrónicas.
Haga lo siguiente: En el ejemplo siguiente se ReadToEndAsync usa y no se bloquea el subproceso durante la lectura.
public class GoodStreamReaderController : Controller
{
[HttpGet("/contoso")]
public async Task<ActionResult<ContosoData>> Get()
{
var json = await new StreamReader(Request.Body).ReadToEndAsync();
return JsonSerializer.Deserialize<ContosoData>(json);
}
}
El código anterior lee de forma asincrónica todo el cuerpo de la solicitud HTTP en la memoria.
Advertencia
Si la solicitud es grande, la lectura de todo el cuerpo de la solicitud HTTP en la memoria podría dar lugar a una condición de memoria fuera de memoria (OOM). OOM puede dar lugar a una denegación de servicio. Para obtener más información, vea Evitar la lectura de cuerpos de solicitud grandes o cuerpos de respuesta en la memoria en este documento.
Haga lo siguiente: El ejemplo siguiente es totalmente asincrónico mediante un cuerpo de solicitud no almacenado en búfer:
public class GoodStreamReaderController : Controller
{
[HttpGet("/contoso")]
public async Task<ActionResult<ContosoData>> Get()
{
return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
}
}
El código anterior deseriala de forma asincrónica el cuerpo de la solicitud en un objeto de C#.
Preferir ReadFormAsync sobre Request.Form
Use HttpContext.Request.ReadFormAsync en lugar de HttpContext.Request.Form.
HttpContext.Request.Form solo se puede leer de forma segura con las condiciones siguientes:
- El formulario se ha leído mediante una llamada a
ReadFormAsync, y - El valor del formulario almacenado en caché se lee mediante
HttpContext.Request.Form
No haga lo siguiente: En el ejemplo siguiente se usa HttpContext.Request.Form . HttpContext.Request.Form usa la sincronización a través de async y puede dar lugar a la inanición del grupo de subprocesos.
public class BadReadController : Controller
{
[HttpPost("/form-body")]
public IActionResult Post()
{
var form = HttpContext.Request.Form;
Process(form["id"], form["name"]);
return Accepted();
}
Haga lo siguiente: En el ejemplo siguiente se HttpContext.Request.ReadFormAsync usa para leer el cuerpo del formulario de forma asincrónica.
public class GoodReadController : Controller
{
[HttpPost("/form-body")]
public async Task<IActionResult> Post()
{
var form = await HttpContext.Request.ReadFormAsync();
Process(form["id"], form["name"]);
return Accepted();
}
Evitar la lectura de cuerpos de solicitud grandes o cuerpos de respuesta en la memoria
En .NET, cada asignación de objetos superior a 85 KB termina en el montón de objetos grandes(LOH). Los objetos grandes son caros de dos maneras:
- El costo de asignación es alto porque se debe borrar la memoria de un objeto grande recién asignado. CLR garantiza que se borra la memoria de todos los objetos recién asignados.
- El LOH se recopila con el resto del montón. LOH requiere una recolección de elementos no utilizados completa o una recolección de Gen2.
En esta entrada de blog se describe el problema de forma concisa:
Cuando se asigna un objeto grande, se marca como objeto Gen 2. No Gen 0 como para objetos pequeños. Las consecuencias son que si se queda sin memoria en loH, GC limpia todo el montón administrado, no solo loH. Por lo tanto, limpia Gen 0, Gen 1 y Gen 2, incluido LOH. Esto se denomina recolección completa de elementos no utilizados y es la recolección de elementos no utilizados que consume más tiempo. Para muchas aplicaciones, puede ser aceptable. Pero definitivamente no para servidores web de alto rendimiento, donde se necesitan pocos búferes de memoria grandes para controlar una solicitud web promedio (leer desde un socket, descomprimir, descodificar JSON & más).
Almacenar de forma naive un cuerpo de solicitud o respuesta grande en un único byte[] o string :
- Puede dar lugar a que se quedándose rápidamente sin espacio en el LOH.
- Puede causar problemas de rendimiento para la aplicación debido a la ejecución completa de los GCs.
Trabajar con una API de procesamiento de datos sincrónica
Cuando se usa un serializador o deserialador que solo admite lecturas y escrituras sincrónicas (por ejemplo, Json.NET):
- Almacenar en búfer los datos en la memoria de forma asincrónica antes de pasarlo al serializador o deserialador.
Advertencia
Si la solicitud es grande, podría dar lugar a una condición de memoria fuera de memoria (OOM). OOM puede dar lugar a una denegación de servicio. Para obtener más información, vea Evitar la lectura de cuerpos de solicitud grandes o cuerpos de respuesta en la memoria en este documento.
ASP.NET Core 3.0 usa System.Text.Json de forma predeterminada para la serialización JSON. System.Text.Json:
- Lee y escribe JSON de forma asincrónica.
- Está optimizado para texto UTF-8.
- Normalmente, mayor rendimiento que
Newtonsoft.Json.
No almacene IHttpContextAccessor.HttpContext en un campo
IHttpContextAccessor.HttpContext devuelve el de la solicitud activa cuando se HttpContext accede desde el subproceso de solicitud. no IHttpContextAccessor.HttpContext debe almacenarse en un campo o variable.
No haga lo siguiente: En el ejemplo siguiente se almacena HttpContext en un campo y, a continuación, se intenta usarlo más adelante.
public class MyBadType
{
private readonly HttpContext _context;
public MyBadType(IHttpContextAccessor accessor)
{
_context = accessor.HttpContext;
}
public void CheckAdmin()
{
if (!_context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
}
El código anterior captura con frecuencia un valor NULL o HttpContext incorrecto en el constructor.
Haga lo siguiente: En el ejemplo siguiente:
- Almacena en IHttpContextAccessor un campo.
- Usa el
HttpContextcampo en el momento correcto y comprueba si esnull.
public class MyGoodType
{
private readonly IHttpContextAccessor _accessor;
public MyGoodType(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public void CheckAdmin()
{
var context = _accessor.HttpContext;
if (context != null && !context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
}
No acceder a HttpContext desde varios subprocesos
HttpContext no es seguro para subprocesos. El acceso desde varios subprocesos en paralelo puede provocar un comportamiento indefinido, como HttpContext bloqueos, bloqueos y daños en los datos.
No haga lo siguiente: En el ejemplo siguiente se hacen tres solicitudes paralelas y se registra la ruta de acceso de la solicitud entrante antes y después de la solicitud HTTP saliente. Se accede a la ruta de acceso de la solicitud desde varios subprocesos, posiblemente en paralelo.
public class AsyncBadSearchController : Controller
{
[HttpGet("/search")]
public async Task<SearchResults> Get(string query)
{
var query1 = SearchAsync(SearchEngine.Google, query);
var query2 = SearchAsync(SearchEngine.Bing, query);
var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);
await Task.WhenAll(query1, query2, query3);
var results1 = await query1;
var results2 = await query2;
var results3 = await query3;
return SearchResults.Combine(results1, results2, results3);
}
private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
{
var searchResults = _searchService.Empty();
try
{
_logger.LogInformation("Starting search query from {path}.",
HttpContext.Request.Path);
searchResults = _searchService.Search(engine, query);
_logger.LogInformation("Finishing search query from {path}.",
HttpContext.Request.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed query from {path}",
HttpContext.Request.Path);
}
return await searchResults;
}
Haga lo siguiente: En el ejemplo siguiente se copian todos los datos de la solicitud entrante antes de realizar las tres solicitudes paralelas.
public class AsyncGoodSearchController : Controller
{
[HttpGet("/search")]
public async Task<SearchResults> Get(string query)
{
string path = HttpContext.Request.Path;
var query1 = SearchAsync(SearchEngine.Google, query,
path);
var query2 = SearchAsync(SearchEngine.Bing, query, path);
var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);
await Task.WhenAll(query1, query2, query3);
var results1 = await query1;
var results2 = await query2;
var results3 = await query3;
return SearchResults.Combine(results1, results2, results3);
}
private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
string path)
{
var searchResults = _searchService.Empty();
try
{
_logger.LogInformation("Starting search query from {path}.",
path);
searchResults = await _searchService.SearchAsync(engine, query);
_logger.LogInformation("Finishing search query from {path}.", path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed query from {path}", path);
}
return await searchResults;
}
No use HttpContext una vez completada la solicitud.
HttpContextsolo es válido siempre que haya una solicitud HTTP activa en la canalización ASP.NET Core ejecución. Toda la ASP.NET Core es una cadena asincrónica de delegados que ejecuta cada solicitud. Cuando se Task completa el devuelto de esta cadena, se recicla HttpContext .
No haga lo siguiente: En el ejemplo siguiente se async void usa , que hace que la solicitud HTTP se complete cuando se alcanza la await primera:
- Lo que siempre es una mala práctica en ASP.NET Core aplicaciones.
- Accede a una
HttpResponsevez completada la solicitud HTTP. - Bloquea el proceso.
public class AsyncBadVoidController : Controller
{
[HttpGet("/async")]
public async void Get()
{
await Task.Delay(1000);
// The following line will crash the process because of writing after the
// response has completed on a background thread. Notice async void Get()
await Response.WriteAsync("Hello World");
}
}
Haga lo siguiente: En el ejemplo siguiente se devuelve un al marco de trabajo, por lo que la solicitud HTTP no Task se completa hasta que se completa la acción.
public class AsyncGoodTaskController : Controller
{
[HttpGet("/async")]
public async Task Get()
{
await Task.Delay(1000);
await Response.WriteAsync("Hello World");
}
}
No capture HttpContext en subprocesos en segundo plano
No haga lo siguiente: En el ejemplo siguiente se muestra un cierre que captura HttpContext de la Controller propiedad . Esto es una mala práctica porque el elemento de trabajo podría:
- Se ejecuta fuera del ámbito de solicitud.
- Intente leer el error
HttpContext.
[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
var path = HttpContext.Request.Path;
Log(path);
});
return Accepted();
}
Haga lo siguiente: En el ejemplo siguiente:
- Copia los datos necesarios en la tarea en segundo plano durante la solicitud.
- No hace referencia a nada del controlador.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
string path = HttpContext.Request.Path;
_ = Task.Run(async () =>
{
await Task.Delay(1000);
Log(path);
});
return Accepted();
}
Las tareas en segundo plano deben implementarse como servicios hospedados. Para más información, consulte Tareas en segundo plano con servicios hospedados.
No capturar servicios que se insertan en los controladores en subprocesos en segundo plano
No haga lo siguiente: En el ejemplo siguiente se muestra que un cierre está capturando a DbContext partir del Controller parámetro action. Esto es una mala práctica. El elemento de trabajo podría ejecutarse fuera del ámbito de la solicitud. el ContosoDbContext objeto tiene como ámbito la solicitud, lo que da lugar a un ObjectDisposedException .
[HttpGet("/fire-and-forget-1")]
public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
});
return Accepted();
}
Haga lo siguiente: En el ejemplo siguiente:
- Inserta un para IServiceScopeFactory crear un ámbito en el elemento de trabajo en segundo plano.
IServiceScopeFactoryes un singleton. - Crea un nuevo ámbito de inserción de dependencias en el subproceso en segundo plano.
- No hace referencia a nada del controlador.
- No captura de
ContosoDbContextla solicitud entrante.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
El código resaltado siguiente:
- Crea un ámbito para la duración de la operación en segundo plano y resuelve los servicios a partir de ella.
- Usa
ContosoDbContextdesde el ámbito correcto.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
No modifique el código de estado ni los encabezados después de que se haya iniciado el cuerpo de la respuesta.
ASP.NET Core almacena en búfer el cuerpo de la respuesta HTTP. La primera vez que se escribe la respuesta:
- Los encabezados se envían junto con ese fragmento del cuerpo al cliente.
- Ya no es posible cambiar los encabezados de respuesta.
No haga lo siguiente: El código siguiente intenta agregar encabezados de respuesta después de que la respuesta ya se haya iniciado:
app.Use(async (context, next) =>
{
await next();
context.Response.Headers["test"] = "test value";
});
En el código anterior, context.Response.Headers["test"] = "test value"; producirá una excepción si next() ha escrito en la respuesta.
Haga lo siguiente: En el ejemplo siguiente se comprueba si la respuesta HTTP se ha iniciado antes de modificar los encabezados.
app.Use(async (context, next) =>
{
await next();
if (!context.Response.HasStarted)
{
context.Response.Headers["test"] = "test value";
}
});
Haga lo siguiente: En el ejemplo siguiente se usa para establecer los encabezados antes de que los HttpResponse.OnStarting encabezados de respuesta se vacían en el cliente.
Comprobar si la respuesta no se ha iniciado permite registrar una devolución de llamada que se invocará justo antes de que se escriban los encabezados de respuesta. Comprobación de si la respuesta no se ha iniciado:
- Proporciona la capacidad de anexar o invalidar encabezados just-in-time.
- No requiere conocimiento del siguiente middleware en la canalización.
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
context.Response.Headers["someheader"] = "somevalue";
return Task.CompletedTask;
});
await next();
});
No llame a next() si ya ha empezado a escribir en el cuerpo de la respuesta.
Solo se espera que se llame a los componentes si es posible que controlen y manipulen la respuesta.
Uso del hospedaje en proceso con IIS
Con el hospedaje en proceso, una aplicación ASP.NET Core se ejecuta en el mismo proceso que su proceso de trabajo de IIS. El hospedaje en proceso proporciona un rendimiento mejorado sobre el hospedaje fuera de proceso porque las solicitudes no se procesan mediante proxy a través del adaptador de bucles inversos. El adaptador de bucle atrás es una interfaz de red que devuelve el tráfico de red saliente a la misma máquina. IIS controla la administración de procesos con el Servicio de activación de procesos de Windows (WAS).
Los proyectos de forma predeterminada son el modelo de hospedaje en proceso ASP.NET Core 3.0 y versiones posteriores.
Para obtener más información, vea Host ASP.NET Core on Windows with IIS