10

高速で継続的な読み取り/書き込みループを使用してネットワーク デバイスと通信する C#、UWP 10 ソリューションを開発しています。API によって提供される StreamSocket はうまく機能しているように見えましたが、メモリ リークが発生していることに気付きましたTask<uint32>。1 分間に数百単位のメモリ リークがヒープに蓄積されています。

内で単純な古いwhile (true)ループを使用するか、TPL Dataflow でasync Task自己投稿を使用するかに関係なく (この回答ActionBlock<T>に従って)、結果は同じです。

ソケットからの読み取りをなくして書き込みに集中すれば、問題をさらに切り分けることができます。DataWriter.StoreAsyncアプローチを使用するか、より直接的なを使用するかに関係なくStreamSocket.OutputStream.WriteAsync(IBuffer buffer)、問題は残ります。さらに、.AsTask()これらに を追加しても違いはありません。

ガベージ コレクタが実行されても、これらTask<uint32>の はヒープから削除されません。これらのタスクはすべて完了しており ( RanToCompletion)、エラーや、「再利用の準備が整っていない」ことを示すその他のプロパティ値はありません。

このページに私の問題のヒントがあるようです(バイト配列が管理された世界から管理されていない世界に移動すると、メモリの解放が妨げられます)、規定された解決策は非常に厳しいようです: これを回避する唯一の方法は、すべての通信ロジックをC++/CX。これが真実ではないことを願っています。確かに、他の C# 開発者は、メモリ リークのない継続的な高速ネットワーク通信を成功裏に実現しています。確かに Microsoft は、C++/CX でメモリ リークなしでのみ機能する API をリリースしないでしょう。

編集

リクエストに応じて、いくつかのサンプル コード。私自身のコードにはレイヤーが多すぎますが、この Microsoft サンプルでは、はるかに単純な例を見ることができます。問題を強調するために、ループで 1000 回送信するように簡単な変更を加えました。これは関連するコードです:

public sealed partial class Scenario3 : Page
{
    // some code omitted

    private async void SendHello_Click(object sender, RoutedEventArgs e)
    {
        // some code omitted

        StreamSocket socket = //get global object; socket is already connected

        DataWriter writer = new DataWriter(socket.OutputStream);

        for (int i = 0; i < 1000; i++)
        {
            string stringToSend = "Hello";
            writer.WriteUInt32(writer.MeasureString(stringToSend));
            writer.WriteString(stringToSend);
            await writer.StoreAsync();
        }
    }
}

Task<UInt32>アプリを起動してソケットを接続すると、ヒープ上に のインスタンスしかありません。「SendHello」ボタンをクリックすると、86 個のインスタンスがあります。2回目押したら129回。

編集#2 アプリ(タイトループの送受信を使用)を3時間実行した後、間違いなく問題あることがわかります.50万個のタスクインスタンスがGCされず、アプリのプロセスメモリが初期から上昇しました46MB~105MB。明らかに、このアプリは無期限に実行できません。 ただし...これはデバッグモードでの実行にのみ適用されます。アプリをリリースモードでコンパイルし、展開して実行すると、メモリの問題は発生しません。一晩中実行したままにできますが、メモリが適切に管理されていることは明らかです。ケースを閉じました。

4

2 に答える 2

10

86 のインスタンスがあります。2回目押したら129回。

それは完全に正常です。ここでの本当の問題は、メモリ プロファイラー レポートを正しく解釈する方法がわからないことです。

タスクは非常に高価なオブジェクトのように聞こえます。これは、費用対効果が高く、これまでに作成された中で最も高価なオペレーティング システム オブジェクトであるスレッドが関与しています。しかし、そうではありません。Task オブジェクトは実際にはちっぽけなオブジェクトです。32 ビット モードでは 44 バイト、64 ビット モードでは 80 バイトしか必要ありません。本当に高価なリソースはタスクによって所有されておらず、スレッドプール マネージャーが処理します。

つまり、コレクションをトリガーするために GC ヒープに十分な圧力をかける前に、多数の Task オブジェクトを作成できます。32 ビット モードで gen #0 セグメントを埋めるために、約47,000個。サーバー上ではさらに多く、数十万、そのセグメントははるかに大きくなります。

コード スニペットでは、実際に作成するオブジェクトは Task オブジェクトだけです。したがって、 for(;;) ループは、 Task オブジェクトの数が減少または制限されるのを見るのに十分な頻度でループすることはほとんどありません。

したがって、.NET Framework にリークがあるという非難はよくあることです。特に、数か月にわたって実行されるサーバー スタイルのアプリで頻繁に使用されるこの種の必須オブジェクト タイプについては、非常に誇張されています。ガベージ コレクターを二重に推測するのは常に注意が必要です。通常、実際にアプリを数か月間実行し、OOM で失敗しないことによってのみ、自信が得られます。

于 2015-10-10T14:30:48.367 に答える
0

for 内で DataWriter を作成して閉じます。

于 2015-10-14T16:11:02.530 に答える