サーバー側の Blazor アプリのホストおよびデプロイ

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

この記事では、ASP.NET Core を使用してサーバー側の Blazor アプリをホストおよびデプロイする方法について説明します。

ホストの構成値

サーバー側の Blazor アプリでは、汎用ホスト構成値を受け入れることができます。

展開

サーバー側のホスティング モデルを使用する場合、Blazor はサーバー上で ASP.NET Core アプリ内から実行されます。 UI の更新、イベント処理、JavaScript の呼び出しは、SignalR 接続経由で処理されます。

ASP.NET Core アプリをホストできる Web サーバーが必要です。 Visual Studio には、サーバー側のアプリ プロジェクト テンプレートが含まれています。 Blazor プロジェクト テンプレートについて詳しくは、「ASP.NET Core Blazor プロジェクトの構造」をご覧ください。

スケーラビリティ

1 台のサーバーのスケーラビリティ (スケールアップ) を検討する場合、アプリに使用できるメモリは、ユーザーの要求が増えたときにアプリによって使い果たされる最初のリソースになります。 サーバー上の使用可能なメモリは、以下の影響を受けます。

  • サーバーがサポートできるアクティブ回線の数。
  • クライアントでの UI の待機時間。

セキュリティで保護されたスケーラブルなサーバー側 Blazor アプリをビルドするためのガイダンスについては、次のリソースを参照してください。

各回線では、最小限の Hello World スタイルのアプリに約 250 KB のメモリが使用されます。 回線のサイズは、アプリのコードと各コンポーネントに関連付けられている状態の保守要件によって変わります。 アプリとインフラストラクチャの開発時にはリソースのニーズを測定することをお勧めしますが、展開ターゲットを計画する際に、次のベースラインを出発点にすることができます。アプリで 5,000 人の同時ユーザーをサポートすることを想定している場合は、アプリに対して少なくとも 1.3 GB のサーバー メモリ (またはユーザーあたり最大 273 KB) の予算を割り当てること検討してください。

SignalR 構成

SignalR のホスティングとスケーリングの条件は、SignalR を使用する Blazor アプリに適用されます。

Blazor アプリでの SignalR の構成ガイダンスなどの詳細については、「ASP.NET Core BlazorSignalR ガイダンス」を参照してください。

トランスポート

Blazor は、待ち時間の短縮、信頼性の向上、およびセキュリティの強化のために、WebSocket を SignalR トランスポートとして使用すれば最も優れた性能を発揮します。 WebSocket が使用できない場合や、ロング ポーリングを使用するようにアプリが明示的に構成されている場合は、SignalR によってロング ポーリングが使用されます。 Azure App Service にデプロイする場合は、サービスの Azure portal 設定で WebSocket を使用するようにアプリを構成します。 Azure App Service 用にアプリを構成する方法の詳細については、SignalR の発行ガイドラインを参照してください。

ロング ポーリングが利用されている場合、コンソール警告が表示されます。

ロング ポーリング フォールバック トランスポートを使用して WebSocket 経由で接続できませんでした。 接続をブロックしている VPN またはプロキシが原因である可能性があります。

グローバル展開と接続エラー

地理的データ センターへのグローバル デプロイに関する推奨事項は次のとおりです。

  • ユーザーの大部分が存在しているリージョンにアプリをデプロイします。
  • 大陸間のトラフィックの待機時間の増加を考慮します。 再接続 UI の表示を制御するには、「ASP.NET Core BlazorSignalR ガイダンス」を参照してください。
  • Azure ホスティングの場合は、Azure SignalR Service を使用します。

Azure SignalR Service

対話型のサーバー側レンダリングを採用する Blazor Web アプリの場合は、Azure SignalR Service の使用を検討してください。 このサービスは、アプリの Blazor Hub と連携して、多数の同時 SignalR 接続までスケールアップします。 さらに、 サービスのグローバル リーチとハイパフォーマンスのデータ センターは、地理的条件による待機時間の短縮に役立ちます。 ホスティング環境でこれらの問題に既に対処している場合は、Azure SignalR Service を使う必要はありません。

Azure SignalR Service の使用を検討してください。アプリの Blazor Hub と連携して、多数の同時 SignalR 接続までスケールアップすることができます。 さらに、 サービスのグローバル リーチとハイパフォーマンスのデータ センターは、地理的条件による待機時間の短縮に役立ちます。 ホスティング環境でこれらの問題に既に対処している場合は、Azure SignalR Service を使う必要はありません。

重要

WebSocket が無効になっている場合、Azure App Service によって、リアルタイム接続のシミュレーションが HTTP ロング ポーリングを使用して行われます。 HTTP ロング ポーリングは、WebSocket を有効にして、クライアントとサーバー間の接続のシミュレーションにポーリングを使用せずに実行する場合よりも著しく遅くなります。 ロング ポーリングを使用する必要がある場合は、最大ポーリング間隔 (MaxPollIntervalInSeconds) の構成が必要なときがあります。これは、サービスが WebSocket からロング ポーリングにフォールバックした場合に、Azure SignalR Service のロング ポーリング接続で許可される最大ポーリング間隔を定義します。 次のポーリング要求が MaxPollIntervalInSeconds 内に発生しない場合、Azure SignalR Service はクライアント接続をクリーンアップします。 Azure SignalR Service は、書き込みバッファー サイズに対するキャッシュされた待機が 1 MB を超えたときにも、サービスのパフォーマンスを確保するために接続をクリーンアップすることに注意してください。 MaxPollIntervalInSeconds の既定値は 5 秒です。 この設定は 1-300 秒に制限されています。

Azure App Service にデプロイされたサーバー側の Blazor アプリには WebSocket を使用することをお勧めします。 Azure SignalR Service では、既定で WebSocket が使用されます。 アプリが Azure SignalR サービスを使用していない場合は、「Azure App Service に ASP.NET Core SignalR アプリを発行する」をご覧ください。

詳細については、次を参照してください。

構成

Azure SignalR サービス用にアプリを構成するには、そのアプリで "スティッキー セッション" をサポートする必要があります。それにより、クライアントはプリレンダリングのときに同じサーバーにリダイレクトされます。 ServerStickyMode オプションまたは構成値は Required に設定されます。 通常、アプリでは次の方法のいずれか 1 つを使用して構成を作成します。

  • Program.cs:

    builder.Services.AddSignalR().AddAzureSignalR(options =>
    {
        options.ServerStickyMode = 
            Microsoft.Azure.SignalR.ServerStickyMode.Required;
    });
    
  • 構成 (次の方法のいずれかを使用):

    • appsettings.json:

      "Azure:SignalR:ServerStickyMode": "Required"
      
    • Azure portal で App Service の [構成]>[アプリケーションの設定] (名前: Azure__SignalR__ServerStickyMode: Required)。 この方法は、Azure SignalR サービスをプロビジョニングする場合に、アプリに自動的に適用されます。

Note

Azure SignalR サービスでスティッキー セッションを有効にしていないアプリからは次のエラーがスローされます。

blazor.server.js:1 Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed. (blazor.server.js:1 捕捉されない (promise 内の) エラー: 基となる接続が閉じられたため、呼び出しが取り消されました。)

Azure SignalR サービスをプロビジョニングする

Visual Studio でアプリに合わせて Azure SignalR サービスをプロビジョニングするには、次のようにします。

  1. アプリ用に、Visual Studio に Azure アプリ発行プロファイルを作成する。
  2. プロファイルに Azure SignalR Service の依存関係を追加する。 Azure サブスクリプションに、アプリに割り当てる既存の Azure SignalR Service のインスタンスがない場合は、 [新しい Azure SignalR Service のインスタンスを作成する] を選択して新しいサービス インスタンスをプロビジョニングします。
  3. Azure にアプリを公開します。

Visual Studio で Azure SignalR サービスを自動的にプロビジョニングすると、"スティッキー セッション" が有効になり、SignalR 接続文字列が App Service の構成に追加されます。

Azure Container Apps でのスケーラビリティ

Azure Container Apps でサーバー側の Blazor アプリをスケーリングするには、Azure SignalR Service の使用に加えて、特定の考慮事項が必要です。 要求ルーティングの処理方法により、すべてのコンテナー インスタンスがアクセスできる一元的な場所にキーを保持するように ASP.NET Core データ保護サービスを構成する必要があります。 キーは Azure Blob Storage に格納し、Azure Key Vault を使って保護できます。 データ保護サービスでは、そのキーを使用して Razor コンポーネントを逆シリアル化します。

Note

このシナリオの詳細とコンテナー アプリのスケーリングについては、Azure での ASP.NET Core アプリのスケーリングに関する記事を参照してください。 このチュートリアルでは、Azure Container Apps でアプリをホストするために必要なサービスを作成して統合する方法について説明しています。 基本的な手順についてはこのセクションでも説明します。

  1. Azure Blob Storage と Azure Key Vault を使用するようにデータ保護サービスを構成するには、次の NuGet パッケージを参照してください。

    Note

    .NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

  2. 次の強調表示されているコードを使用して、Program.cs を更新します。

    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();
    

    上記の変更により、アプリでは一元化されたスケーラブルなアーキテクチャを使用してデータ保護を管理できるようになります。 DefaultAzureCredential は、コードが Azure にデプロイされた後にコンテナー アプリのマネージド ID を検出し、それを使用して BLOB ストレージとアプリのキー コンテナーに接続します。

  3. コンテナー アプリのマネージド ID を作成し、BLOB ストレージとキー コンテナーへのアクセス権を付与するには、次の手順を実行します。

    1. Azure portal で、コンテナー アプリの概要ページに移動します。
    2. 左側のナビゲーションから [サービス コネクタ] を選択します
    3. 上部のナビゲーションから [+ 作成] を選択します。
    4. [接続の作成] ポップアップ メニューで、次の値を入力します。
      • [コンテナー]: アプリをホストするために作成したコンテナー アプリを選択します。
      • [サービスの種類]: [Blob Storage] を選択します。
      • [サブスクリプション]: コンテナー アプリを所有するサブスクリプションを選択します。
      • [接続名]: scalablerazorstorage の名前を入力します。
      • [クライアントの種類]: [.NET] を選択し、[次へ] を選択します。
    5. [システム割り当てマネージド ID] を選択し、[次へ] を選択します。
    6. 既定のネットワーク設定を使用し、[次へ] を選択します。
    7. Azure によって設定が検証された後、[作成] を選択します。

    キー コンテナーに対して上記の設定を繰り返します。 [基本] タブで、適切なキー コンテナー サービスとキーを選択します。

Azure SignalR Service を使用しないAzure App Service

Azure App Service 上で対話型のサーバー側レンダリングを使う Blazor Web アプリをホストするには、アプリケーション要求ルーティング (ARR) アフィニティと WebSocket の構成が必要です。 また、UI の待機時間を短縮するために、App Service を適切にグローバルに分散する必要があります。 Azure App Service 上でホストする場合は、Azure SignalR Service を使う必要はありません。

Azure App Service 上で Blazor Server アプリをホストするには、アプリケーション要求ルーティング (ARR) アフィニティと WebSocket の構成が必要です。 また、UI の待機時間を短縮するために、App Service を適切にグローバルに分散する必要があります。 Azure App Service 上でホストする場合は、Azure SignalR Service を使う必要はありません。

アプリを構成するには、次のガイダンスを使用します。

IIS

IIS を使用する場合は、次を有効にします。

Kubernetes

次のスティッキー セッションに対する Kubernetes 注釈を使用して、イングレス定義を作成します。

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"

Nginx を使用した Linux

ASP.NET Core SignalR アプリ」のガイダンスに従って、次のように変更します。

  • location のパスを /hubroute (location /hubroute { ... }) からルート パス / (location / { ... }) に変更します。
  • プロキシ バッファーリング (proxy_buffering off;) の構成は削除します。この設定は、Blazor アプリのクライアントとサーバー間の通信に関連しないサーバー送信イベント (SSE) にのみ適用されるためです。

詳細および構成のガイダンスについては、次のリソースを参照してください。

Apache を使用した Linux

Linux 上の Apache の背後に Blazor アプリをホストするには、HTTP および Websocket トラフィック用に ProxyPass を構成します。

次に例を示します。

  • Kestrel サーバーは、ホスト コンピューター上で実行されています。
  • このアプリは、ポート 5000 でトラフィックをリッスンします。
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

次のモジュールを有効にします。

a2enmod   proxy
a2enmod   proxy_wstunnel

WebSockets エラーをブラウザ コンソールで確認します。 エラーの例:

  • Firefox からサーバー (ws://the-domain-name.tld/_blazor?id=XXX) への接続を確立できません。
  • エラー :'WebSockets' の転送の開始に失敗しました。エラー :転送のエラーがありました。
  • エラー :'LongPolling' の転送の開始に失敗しました。TypeError: this.transport は定義されていません
  • エラー :利用可能ないかなる転送を使用しても、サーバーに接続できませんでした。 WebSockets が失敗しました
  • エラー :接続が 'Connected' 状態ではない場合、データは送信できません。

詳細および構成のガイダンスについては、次のリソースを参照してください。

ネットワーク待機時間の測定

次の例で示すように、JS 相互運用を使用してネットワーク待機時間を測定できます。

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();
        }
    }
}

妥当な UI エクスペリエンスには、UI の待機時間を 250 ミリ秒以下にすることをお勧めします。

メモリ管理

サーバーでは、ユーザー セッションごとに新しい回線が作成されます。 各ユーザー セッションは、ブラウザーでの 1 つのドキュメントのレンダリングに対応します。 たとえば、複数のタブで複数のセッションが作成されます。

Blazor は、セッションを開始した "回線" と呼ばれるブラウザーへの一定の接続を維持します。 ユーザーがネットワーク接続を失ったり、ブラウザーを突然閉じたりした場合など、いくつかの理由により、いつでも接続が失われる可能性があります。 接続が失われた場合、Blazor には、"切断された" プールに限られた数の回線を配置する復旧メカニズムがあり、クライアントには再接続してセッションを再確立するための制限時間が与えられます (既定値: 3 分)。

その後、Blazor は回線を解放し、セッションを破棄します。 その時点から、回線はガベージ コレクション (GC) の対象となり、回線の GC 生成のコレクションがトリガーされたときに要求されます。 理解すべき重要な側面の 1 つは、回線の有効期間が長いことです。つまり、回線によってルート指定されたオブジェクトのほとんどが、最終的に Gen 2 に到達します。 その結果、Gen 2 コレクションが発生するまで、これらのオブジェクトが解放されない場合があります。

一般的なメモリ使用量を測定する

前提条件:

  • アプリはリリース構成で発行する必要があります。 生成されたコードは運用環境のデプロイに使用されるコードを表していないので、デバッグ構成の測定値は関係ありません。
  • デバッガーをアタッチせずにアプリを実行する必要があります。これが、アプリの動作に影響を与え、結果を損なう可能性があるためです。 Visual Studio で、メニュー バーから [デバッグ]>[デバッグなしで開始] を選択するか、キーボードを使用して Ctrl+F5 キーを押して、デバッグなしでアプリを起動します。
  • .NET で実際に使用されるメモリの量を理解するには、さまざまな種類のメモリを検討してください。 一般に、開発者は Windows OS 上のタスク マネージャーでアプリのメモリ使用量を検査します。これで、通常は、使用中の実際のメモリの上限がわかります。 詳細については、次の記事を参照してください。

Blazor に適用されるメモリ使用量

Blazor によって使用されるメモリは次のように計算します。

(アクティブな回線 × 回線ごとのメモリ) + (切断された回線 × 回線ごとのメモリ)

回線で使用されるメモリの量と、アプリが維持できる潜在的な最大アクティブ回線数は、アプリの記述方法によって大きく異なります。 使用可能な最大アクティブ回線数は、次の方法で大まかにわかります。

使用可能な最大メモリ数 / 回線ごとのメモリ = 潜在的な最大アクティブ回線数

Blazor でメモリ リークが発生するには、次の条件を満たす必要があります。

  • メモリが、アプリではなくフレームワークによって割り当てられる必要がある。 アプリで 1 GB の配列を割り当てる場合、アプリが配列の破棄を管理する必要があります。
  • メモリがアクティブに使用されていない。つまり、回線がアクティブではなく、切断された回線のキャッシュから削除されている。 最大アクティブ回線数が実行されている場合、メモリ不足は、メモリ リークではなく、スケールの問題です。
  • 回線の GC 生成用のガベージ コレクション (GC) が実行されたが、フレームワーク内の別のオブジェクトが回線への強い参照を保持しているため、ガベージ コレクターが回線を要求できない。

それ以外の場合は、メモリ リークはありません。 回線がアクティブ (接続でも切断でも) の場合、回線はまだ使用中です。

回線の GC 生成のコレクションが実行されない場合、ガベージ コレクターがその時点でメモリを解放する必要がないため、メモリは解放されません。

GC 生成のコレクションが実行され、回線が解放された場合は、.NET が仮想メモリをアクティブな状態に維持することを決定する可能性があるため、プロセスではなく GC 統計に対してメモリを検証する必要があります。

メモリが解放されていない場合は、アクティブまたは切断された状態でなく、フレームワーク内の別のオブジェクトによってルート指定されている回線が見つかるはずです。 それ以外の場合、メモリを解放できないのは、開発者コードのアプリの問題です。

メモリ使用量を削減する

アプリのメモリ使用量を削減するには、次のいずれかの方法を採用してください。

  • .NET プロセスで使用されるメモリの総量を制限する。 詳細については、「ガベージ コレクションの実行時構成オプション」を参照してください。
  • 切断された回線の数を減らす。
  • 回線が切断状態になるまでの時間を短縮する。
  • ダウンタイム期間中にガベージ コレクションを手動でトリガーしてコレクションを実行する。
  • サーバー モードではなく、ガベージ コレクションを積極的にトリガーするワークステーション モードでガベージ コレクションを構成する。

一部のモバイル デバイスのブラウザーのヒープ サイズ

クライアント上で実行され、モバイル デバイスのブラウザー (特に iOS 上の Safari) を対象とする Blazor アプリをビルドする際には、MSBuild プロパティ EmccMaximumHeapSize を使用してアプリの最大メモリを減らすことが求められる場合があります。 詳しくは、「ASP.NET Core Blazor WebAssembly のホストと展開」をご覧ください。

その他のアクションと考慮事項

  • メモリ要求が高い場合はプロセスのメモリ ダンプをキャプチャし、最も多くのメモリを使用しているオブジェクトと、それらのオブジェクトがルート指定されている場所 (それらに対する参照を保持するもの) を特定します。
  • dotnet-counters を使って、アプリでのメモリの動作状況に関する統計情報を調べることができます。 詳しくは、「パフォーマンス カウンターを調べる (dotnet-counters)」をご覧ください。.
  • GC がトリガーされた場合でも、.NET は、近いうちにメモリを再利用する可能性があるため、メモリをすぐに OS に返さずに保持し続けます。 これにより、メモリのコミットとデコミットを絶えず行う必要はなくなりますが、コストがかかります。 dotnet-counters を使っている場合、GC が発生し、使用済みメモリの量が 0 (ゼロ) に低下したのに、ワーキング セット カウンターが減らないのは、このことを反映しており、これは .NET がメモリを再利用のために保持していることを示します。 この動作を制御するためのプロジェクト ファイル (.csproj) 設定の詳細については、「ガベージ コレクションの実行時構成オプション」を参照してください。
  • サーバー GC は、アプリのフリーズを回避するためにそれが絶対に必要であると判断し、ユーザーのアプリだけがマシン上で実行していて、システム内のすべてのメモリを使用できると見なすまで、ガベージ コレクションをトリガーしません。 たとえば、システムに 50 GB がある場合、ガベージ コレクターは、Gen 2 コレクションをトリガーする前に、使用可能なメモリの 50 GB をすべて使用しようとします。
  • 切断された回線の保持構成について詳しくは、「ASP.NET Core BlazorSignalR ガイダンス」を参照してください。

メモリの測定

  • リリース構成でアプリを発行します。
  • アプリの発行済みのバージョンを実行します。
  • 実行中のアプリにデバッガーをアタッチしないでください。
  • Gen 2 を強制的にトリガーしてコレクションを圧縮すると (GC.Collect(2, GCCollectionMode.Aggressive | GCCollectionMode.Forced, blocking: true, compacting: true))、メモリを解放しますか?
  • アプリが大きなオブジェクト ヒープにオブジェクトを割り当てているかどうかを検討します。
  • アプリが要求と処理でウォームアップされた後、メモリの増加をテストしていますか? 通常、コードの初回実行時に設定されて、アプリの占有領域に一定量のメモリを追加するキャッシュがあります。