Hospedar e implementar aplicaciones Blazordel lado del servidor

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

En este artículo se explica cómo hospedar e implementar aplicacionesBlazor del lado servidor mediante ASP.NET Core.

Valores de configuración de host

Las aplicaciones del lado del servidorBlazor pueden aceptarvalores de configuración de host genérico.

Implementación

Mediante un modelo de hospedaje del lado del servidor Blazor se ejecuta en el servidor desde una aplicación ASP.NET Core. Las actualizaciones de la interfaz de usuario, el control de eventos y las llamadas de JavaScript se controlan mediante una conexión SignalR.

Se requiere un servidor web que pueda hospedar una aplicación ASP.NET Core. Visual Studio incluye una plantilla de proyecto de aplicación del lado del servidor. Para más información sobre las plantillas de proyecto de Blazor, vea Estructura del proyecto de Blazor de ASP.NET Core.

Escalabilidad

A la hora de considerar la escalabilidad de un solo servidor (escalado vertical), la memoria disponible para una aplicación es probablemente el primer recurso que la aplicación agota a medida que la demanda de los usuarios aumenta. La memoria disponible en el servidor afecta a lo siguiente:

  • Número de circuitos activos que un servidor puede admitir.
  • Latencia de la interfaz de usuario en el cliente.

Para obtener instrucciones sobre la creación de aplicaciones del lado servidor seguras y escalables de Blazor, consulte los siguientes recursos:

Cada circuito utiliza aproximadamente 250 KB de memoria para una aplicación mínima de estilo Hola mundo. El tamaño de un circuito depende del código de la aplicación y de los requisitos de mantenimiento del estado asociados a cada componente. Se recomienda que mida la demanda de recursos durante el desarrollo de la aplicación y la infraestructura, pero la línea de base siguiente puede ser un punto de partida para planear el destino de implementación: Si espera que la aplicación admita 5000 usuarios simultáneos, considere la posibilidad de presupuestar al menos 1,3 GB de memoria del servidor en la aplicación (o ~273 KB por usuario).

Configuración de SignalR

Las condiciones de hospedaje y escalado de SignalR se aplican a aplicaciones Blazor que usan SignalR.

Para obtener más información sobre las aplicaciones de SignalR enBlazor, incluida la guía de configuración, consulte la Guía deBlazorSignalR ASP.NET Core.

Transportes

Blazor funciona mejor cuando se usa WebSockets como transporte de SignalR debido a su menor latencia, su mayor confiabilidad y su seguridad mejorada. SignalR usa el sondeo largo cuando WebSockets no está disponible o cuando la aplicación está configurada explícitamente para usarlo. Al implementar en Azure App Service, configure la aplicación para usar WebSockets en la configuración de Azure Portal del servicio. Para más información sobre la configuración de la aplicación para Azure App Service, consulte las SignalRdirectrices de publicación de.

Aparece una advertencia en la consola si se utiliza el sondeo largo:

No se pudo conectar a través de WebSockets mediante el transporte de reserva de sondeo largo. Esto puede deberse a que una VPN o un proxy bloquean la conexión.

Errores de implementación global y conexión

Recomendaciones para implementaciones globales en centros de datos geográficos:

  • Implemente la aplicación en las regiones donde residen la mayoría de los usuarios.
  • Tenga en cuenta la mayor latencia para el tráfico entre continentes. Para controlar la apariencia de la interfaz de usuario de reconexión, vea ASP.NET Core BlazorSignalR guía.
  • Para el hospedaje de Azure, use el Azure SignalR Service.

Azure SignalR Service

Para Blazor Web Apps que adoptan la representación interactiva del lado servidor, considere la posibilidad de usar Azure SignalR Service. El servicio funciona junto con el centro de Blazor de la aplicación para escalar verticalmente a un gran número de conexiones simultáneas SignalR. Además, los centros de datos de alto rendimiento y alcance global del servicio son de gran ayuda a la hora de reducir la latencia ocasionada por la geografía. Si el entorno de hospedaje ya controla estos problemas, no es necesario usar Azure SignalR Service.

Considere la posibilidad de usar Azure SignalR Service, que funciona junto con el centro de Blazor de la aplicación para escalar verticalmente a un gran número de conexiones de SignalR simultáneas. Además, los centros de datos de alto rendimiento y alcance global del servicio son de gran ayuda a la hora de reducir la latencia ocasionada por la geografía. Si el entorno de hospedaje ya controla estos problemas, no es necesario usar Azure SignalR Service.

Importante

Cuando los WebSockets están deshabilitados, Azure App Service simula una conexión en tiempo real mediante el sondeo prolongado de HTTP. El sondeo prolongado de HTTP es notablemente más lento que la ejecución con los WebSockets habilitados, que no usa el sondeo para simular una conexión cliente-servidor. En caso de que se deba usar el sondeo largo, es posible que tenga que configurar el intervalo de sondeo máximo (MaxPollIntervalInSeconds), que define el intervalo de sondeo máximo permitido para las conexiones de sondeo largo en Azure SignalR Service si el servicio vuelve de WebSockets al sondeo largo. Si la siguiente solicitud de sondeo no entra en MaxPollIntervalInSeconds, Azure SignalR Service limpia la conexión de cliente. Tenga en cuenta que Azure SignalR Service también limpia las conexiones cuando se almacenan en caché en espera de que el tamaño del búfer de escritura sea mayor que 1 MB para garantizar el rendimiento del servicio. El valor predeterminado de MaxPollIntervalInSeconds es 5 segundos. La configuración se limita de 1 a 300 segundos.

Se recomienda el uso de WebSockets para aplicaciones del lado del servidor Blazor implementadas en Azure App Service. Azure SignalR Service usa WebSockets de forma predeterminada. Si la aplicación no usa Azure SignalR Service, vea Publicación de una aplicación ASP.NET Core SignalR en Azure App Service.

Para más información, consulte:

Configuración

A fin de configurar una aplicación para Azure SignalR Service, la aplicación debe admitir sesiones permanentes, en las que se devuelve a los clientes al mismo servidor durante la representación previa. La opción ServerStickyMode o el valor de configuración se establecen en Required. Normalmente, una aplicación crea la configuración mediante uno de los enfoques siguientes:

  • Program.cs:

    builder.Services.AddSignalR().AddAzureSignalR(options =>
    {
        options.ServerStickyMode = 
            Microsoft.Azure.SignalR.ServerStickyMode.Required;
    });
    
  • Configuración (use uno de los enfoques siguientes):

    • En appsettings.json:

      "Azure:SignalR:ServerStickyMode": "Required"
      
    • El valor Configuración>Configuración de la aplicación de la instancia de App Service en Azure Portal (Nombre: Azure__SignalR__ServerStickyMode, Valor: Required). Este enfoque se adopta automáticamente para la aplicación si se aprovisiona Azure SignalR Service.

Nota

El siguiente error lo produce una aplicación que no ha habilitado sesiones permanentes para Azure SignalR Service:

blazor.server.js:1 Uncaught (in promise) Error: invocación cancelada debido a que se cerró la conexión subyacente.

Aprovisionamiento de Azure SignalR Service

A fin de aprovisionar Azure SignalR Service para una aplicación en Visual Studio, haga lo siguiente:

  1. Cree un perfil de publicación de aplicaciones de Azure en Visual Studio para la aplicación .
  2. Agregue la dependencia de Azure SignalR Service al perfil. Si la suscripción de Azure no tiene una instancia de Azure SignalR Service para asignarla a la aplicación, seleccione Crear una instancia de Azure SignalR Service para aprovisionar una nueva instancia de servicio.
  3. Publicación de la aplicación en Azure.

El aprovisionamiento de Azure SignalR Service en Visual Studio habilita sesiones permanentes de forma automática y agrega la cadena de conexión SignalR a la configuración de App Service.

Escalabilidad en Azure Container Apps

El escalado de aplicacionesBlazordel lado del servidor en Azure Container Apps requiere consideraciones específicas además del uso del ServicioSignalR Azure. Debido a la forma en que se controla el enrutamiento de solicitudes, debe configurarse el servicio de protección de datos ASP.NET Core para conservar las claves en una ubicación centralizada a la que puedan acceder todas las instancias de contenedor. Las claves se pueden almacenar en Azure Blob Storage y protegerse con Azure Key Vault. El servicio de protección de datos usa las claves para deserializar componentes de Razor.

Nota:

Para una exploración más profunda de este escenario y el escalado de aplicaciones de contenedor, consulte Escalado de aplicaciones ASP.NET Core en Azure. En el tutorial se explica cómo crear e integrar los servicios necesarios para hospedar aplicaciones en Azure Container Apps. Los pasos básicos también se proporcionan en esta sección.

  1. Para configurar el servicio de protección de datos para que use Azure Blob Storage y Azure Key Vault, haga referencia a los siguientes paquetes NuGet:

    Nota:

    Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

  2. Actualice Program.cs con el siguiente código resaltado:

    using Azure.Identity;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    var BlobStorageUri = builder.Configuration["AzureURIs:BlobStorage"];
    var KeyVaultURI = builder.Configuration["AzureURIs:KeyVault"];
    
    builder.Services.AddRazorPages();
    builder.Services.AddHttpClient();
    builder.Services.AddServerSideBlazor();
    
    builder.Services.AddAzureClientsCore();
    
    builder.Services.AddDataProtection()
                    .PersistKeysToAzureBlobStorage(new Uri(BlobStorageUri),
                                                    new DefaultAzureCredential())
                    .ProtectKeysWithAzureKeyVault(new Uri(KeyVaultURI),
                                                    new DefaultAzureCredential());
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapRazorPages();
    
    app.Run();
    

    Los cambios anteriores permiten a la aplicación administrar la protección de datos mediante una arquitectura centralizada y escalable. DefaultAzureCredential detecta la identidad administrada de la aplicación contenedora después de implementar el código en Azure y la usa para conectarse a Blob Storage y al almacén de claves de la aplicación.

  3. Para crear la identidad administrada de la aplicación contenedora y concederle acceso a Blob Storage y a un almacén de claves, complete los pasos siguientes:

    1. En Azure Portal, navegue a la página de información general de la aplicación contenedora.
    2. Seleccione Conector de servicio en el panel de navegación de la izquierda.
    3. Seleccione + Crear en el panel de navegación superior.
    4. En el menú de control flotante Crear conexión, escriba los valores siguientes:
      • Contenedor: seleccione la aplicación contenedora que ha creado para hospedar la aplicación.
      • Tipo de servicio: seleccione Blob Storage.
      • Suscripción: seleccione la suscripción que sea propietaria de la aplicación contenedora.
      • Nombre de conexión: escriba un nombre de scalablerazorstorage.
      • Tipo de cliente: seleccione .NET y después Siguiente.
    5. Seleccione Identidad administrada asignada por el sistema y, luego, Siguiente.
    6. Use la configuración de red predeterminada y seleccione Siguiente.
    7. Una vez que Azure valide la configuración, seleccione Crear.

    Repita la configuración anterior para el almacén de claves. Seleccione el servicio y la clave adecuados del almacén de claves en la pestaña Aspectos básicos.

Azure App Service sin servicio SignalR de Azure

Hospedar una aplicación web de Blazor que usa la representación interactiva del lado servidor en Azure App Service requiere la configuración de afinidad de Enrutamiento de solicitudes de aplicación (ARR) y WebSockets. App Service también debe distribuirse de forma adecuada global para reducir la latencia de la interfaz de usuario. No es necesario usar Azure SignalR Service cuando se hospeda en Azure App Service.

El hospedaje de una aplicación Blazor Server en Azure App Service requiere la configuración de la afinidad de enrutamiento de solicitudes de aplicación (ARR) y WebSockets. App Service también debe distribuirse de forma adecuada global para reducir la latencia de la interfaz de usuario. No es necesario usar Azure SignalR Service cuando se hospeda en Azure App Service.

Use las instrucciones siguientes para configurar la aplicación:

IIS

Al usar IIS, habilite:

Kubernetes

Cree una definición de entrada con las siguientes anotaciones de Kubernetes para sesiones permanentes:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: <ingress-name>
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "affinity"
    nginx.ingress.kubernetes.io/session-cookie-expires: "14400"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "14400"

Linux con Nginx

Siga las instrucciones para obtener una aplicación SignalR de ASP.NET Core con los siguientes cambios:

  • Cambie la ruta de acceso location de /hubroute (location /hubroute { ... }) a la ruta de acceso / (location / { ... }).
  • Quite la configuración de almacenamiento en búfer de proxy (proxy_buffering off;) porque la configuración solo se aplica a los eventos enviados por el servidor (SSE), que no son relevantes para las interacciones cliente-servidor de la aplicación Blazor.

Para obtener más información e instrucciones de configuración, vea los recursos siguientes:

Linux con Apache

Para hospedar una aplicación de Blazor en Apache en Linux, configure ProxyPass para el tráfico HTTP y WebSockets.

En el ejemplo siguiente:

  • El servidor de Kestrel se está ejecutando en el equipo host.
  • La aplicación escucha el tráfico en el puerto 5000.
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

Habilite los siguientes módulos:

a2enmod   proxy
a2enmod   proxy_wstunnel

Busque errores de WebSockets en la consola del explorador. Errores de ejemplo:

  • Firefox no puede establecer una conexión con el servidor en ws://the-domain-name.tld/_blazor?id=XXX
  • Error: No se pudo iniciar el transporte 'WebSockets': Error: Error en el transporte.
  • Error: No se pudo iniciar el transporte 'LongPolling': Tipo de error: this.transport no definido
  • Error: No se puede conectar con el servidor con ninguno de los transportes disponibles. Error de WebSockets
  • Error: No se pueden enviar datos si la conexión no está en estado 'Conectado'.

Para obtener más información e instrucciones de configuración, vea los recursos siguientes:

Medición de la latencia de red

La interoperabilidad de JS se puede usar para medir la latencia de red, como se muestra en el ejemplo siguiente.

MeasureLatency.razor:

@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}

Para una experiencia de interfaz de usuario razonable, se recomienda una latencia de interfaz de usuario sostenida de 250 ms o menos.

Administración de memoria

En el servidor, se crea un nuevo circuito para cada sesión de usuario. Cada sesión de usuario corresponde a la representación de un único documento en el explorador. Por ejemplo, varias pestañas crean varias sesiones.

Blazor mantiene una conexión constante con el explorador, denominado circuito, que inició la sesión. Las conexiones pueden perderse en cualquier momento por varias razones, como cuando el usuario pierde la conectividad de red o cierra bruscamente el navegador. Cuando se pierde una conexión, Blazor tiene un mecanismo de recuperación que coloca un número limitado de circuitos en un grupo "desconectado", lo que proporciona a los clientes un tiempo limitado para reconectarse y restablecer la sesión (valor predeterminado: 3 minutos).

Después de ese tiempo, Blazor libera el circuito y descarta la sesión. A partir de ese momento, el circuito es apto para la recolección de elementos no utilizados (GC) y se le reclama cuando se desencadena una recolección para la generación de GC del circuito. Un aspecto importante que hay que comprender es que los circuitos tienen una larga vida útil, lo que significa que la mayoría de los objetos con raíz en el circuito acaban llegando a Gen 2. Como resultado, es posible que no vea esos objetos liberados hasta que se produzca una recolección de Gen 2.

Medición del uso de memoria en general

Requisitos previos:

  • La aplicación debe publicarse en la configuración de versión. Las medidas de configuración de depuración no son pertinentes, ya que el código generado no es representativo del código usado para una implementación de producción.
  • La aplicación debe ejecutarse sin un depurador asociado, ya que esto también podría afectar al comportamiento de la aplicación y estropear los resultados. En Visual Studio, inicie la aplicación sin depurar seleccionando Depurar>Iniciar sin depurar en la barra de menús o Ctrl+F5 con el teclado.
  • Tenga en cuenta los diferentes tipos de memoria para comprender la cantidad de memoria que realmente usa .NET. Por lo general, los desarrolladores inspeccionan el uso de memoria de la aplicación en el Administrador de tareas del sistema operativo Windows, que normalmente ofrece un límite superior de la memoria real en uso. Para obtener más información, vea los artículos siguientes:

Uso de memoria aplicado a Blazor

Calculamos la memoria usada por Blazor de la siguiente manera:

(Circuitos activos × memoria por circuito) + (circuitos desconectados × memoria por circuito)

La cantidad de memoria que usa un circuito y el máximo potencial de circuitos activos que una aplicación puede mantener depende en gran medida de cómo se escribe la aplicación. El número máximo de posibles circuitos activos se describe aproximadamente mediante:

Memoria máxima disponible / Memoria por circuito = Máximo potencial de circuitos activos

Para que se produzca una fuga de memoria en Blazor, debe cumplirse lo siguiente:

  • El marco debe asignar la memoria, no la aplicación. Si asigna una matriz de 1 GB en la aplicación, la aplicación debe administrar la eliminación de la matriz.
  • La memoria no debe usarse activamente, lo que significa que el circuito no está activo y se ha expulsado de la memoria caché de circuitos desconectados. Si tiene los circuitos activos máximos en ejecución, quedarse sin memoria es un problema de escala, no una pérdida de memoria.
  • Se ha ejecutado una recolección de elementos no utilizados (GC) para la generación de GC del circuito, pero el recolector de elementos no utilizados no ha podido reclamar el circuito porque otro objeto del marco contiene una referencia fuerte al circuito.

En otros casos, no hay pérdida de memoria. Si el circuito está activo (conectado o desconectado), el circuito todavía está en uso.

Si no se ejecuta una recolección para la generación de GC del circuito, la memoria no se libera porque el recolector de elementos no utilizados no necesita liberar la memoria en ese momento.

Si se ejecuta una recolección de una generación de GC y libera el circuito, debe validar la memoria con las estadísticas de GC, no con el proceso, ya que .NET podría decidir mantener activa la memoria virtual.

Si la memoria no se libera, debe encontrar un circuito que no esté activo o desconectado y que tenga su raíz en otro objeto del marco. En cualquier otro caso, la imposibilidad de liberar memoria es un problema de aplicación en el código del desarrollador.

Reducción del uso de memoria

Adopte cualquiera de las siguientes estrategias para reducir el uso de memoria de una aplicación:

  • Limite la cantidad total de memoria usada por el proceso de .NET. Para obtener más información, consulte Opciones de configuración de ejecución para la recolección de elementos no utilizados.
  • Reduzca el número de circuitos desconectados.
  • Reduzca el tiempo que se permite que un circuito esté en estado desconectado.
  • Desencadene manualmente una recolección de elementos no utilizados para realizar una recolección durante períodos de inactividad.
  • Configure la recolección de elementos no utilizados en modo Estación de trabajo, que desencadena agresivamente la recolección de elementos no utilizados, en lugar de en modo servidor.

Tamaño del montón para algunos exploradores de dispositivos móviles

Al compilar una aplicación Blazor que se ejecuta en el cliente y tiene como destino exploradores de dispositivos móviles, especialmente Safari en iOS, es posible que sea necesario reducir la memoria máxima de la aplicación con la propiedad MSBuild EmccMaximumHeapSize. Para obtener más información, vea Hospedaje e implementación de Blazor WebAssembly en ASP.NET Core.

Acciones y consideraciones adicionales

  • Capture un volcado de memoria del proceso cuando las demandas de memoria sean altas e identifique los objetos que consumen la mayor parte de la memoria y dónde tienen la raíz esos objetos (qué contiene una referencia a ellos).
  • Puedes examinar las estadísticas sobre cómo se comporta la memoria de la aplicación mediante dotnet-counters. Para más información, vea Investigación de los contadores de rendimiento (dotnet-counters).
  • Incluso cuando se desencadena la recolección de elementos no utilizados, .NET se mantiene en la memoria en lugar de devolverse al sistema operativo inmediatamente, ya que es probable que reutilice la memoria en un futuro próximo. Esto evita confirmar y anular la confirmación de la memoria constantemente, lo que es costoso. Lo verá reflejado si usa dotnet-counters porque verá que se produce la recolección de elementos no utilizados y la cantidad de memoria usada baja a 0 (cero), pero no verá la disminución del contador del conjunto de trabajo, que es la señal de .NET se mantiene en la memoria para reutilizarlo. Para obtener más información sobre la configuración del archivo de proyecto (.csproj) para controlar este comportamiento, consulte Opciones de configuración de ejecución para la recolección de elementos no utilizados.
  • La recolección de elementos no utilizados del servidor no desencadena recolecciones de elementos no utilizados hasta que determina que es absolutamente necesario hacerlo para evitar el bloqueo de la aplicación y considera que la aplicación es lo único que se ejecuta en la máquina, para que pueda usar toda la memoria del sistema. Si el sistema tiene 50 GB, el recolector de elementos no utilizados busca usar la memoria disponible completa de 50 GB antes de desencadenar una recolección de Gen 2.
  • Para obtener información sobre la configuración de retención de circuitos desconectados, vea las instrucciones de ASP.NET Core BlazorSignalR.

Medición de la memoria

  • Publique la aplicación en Configuración de versión.
  • Ejecute una versión publicada de la aplicación.
  • No adjunte el depurador a la aplicación en ejecución.
  • ¿El desencadenamiento forzado de Gen 2 compacta la colección (GC.Collect(2, GCCollectionMode.Aggressive | GCCollectionMode.Forced, blocking: true, compacting: true)) libera la memoria?
  • Considere si la aplicación asigna objetos en el montón de objetos grandes.
  • ¿Va a probar el crecimiento de memoria después de que la aplicación se active con las solicitudes y el procesamiento? Normalmente, hay cachés que se rellenan cuando el código se ejecuta por primera vez que agregan una cantidad constante de memoria a la superficie de la aplicación.