13

アプリケーションの 1 つで見られる問題を、信じられないほど単純な再現サンプルに要約しました。何かが間違っているか、何かが欠けているかどうかを知る必要があります。

とにかく、以下はコードです。この動作は、コードが実行され、OutOfMemoryException でクラッシュするまでメモリ内で着実に増加するというものです。これにはしばらく時間がかかりますが、動作としては、オブジェクトが割り当てられ、ガベージ コレクションは行われません。

私はメモリ ダンプを取得し、!gcroot を実行したり、ANTS を使用して問題を特定したりしましたが、しばらく作業を続けており、新しい目が必要です。

この再現サンプルは、Canvas を作成し、それに Line を追加する単純なコンソール アプリケーションです。これは継続的に行われます。これがコードのすべてです。システムが応答しなくなるほど CPU に負担がかからないようにするため (そして、GC が実行できないという奇妙な事態が発生しないようにするため)、時々スリープします。

誰にも考えはありますか?.NET 3.0 のみ、.NET 3.5、および .NET 3.5 SP1 でこれを試しましたが、3 つの環境すべてで同じ動作が発生しました。

また、このコードを WPF アプリケーション プロジェクトにも配置し、ボタン クリックでコードをトリガーしたことにも注意してください。

システムを使用する;
System.Collections.Generic の使用;
System.Linq を使用します。
System.Text を使用します。
System.Windows.Controls を使用します。
System.Windows.Shapes を使用します。
System.Windows を使用します。

名前空間 SimplestReproSample
{
    クラス プログラム
    {
        [スタスレッド]
        static void Main(string[] args)
        {
            長いカウント = 0;
            while (真)
            {
                もし (count++ % 100 == 0)
                {
                    // CPU 全体を使い果たしていないことを確認するために、しばらくスリープします
                    System.Threading.Thread.Sleep(50);
                }
                BuildCanvas();
            }
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        private static void BuildCanvas()
        {
            Canvas c = new Canvas();

            行 line = new Line();
            line.X1 = 1;
            line.Y1 = 1;
            line.X2 = 100;
            line.Y2 = 100;
            線幅 = 100;
            c.Children.Add(行);

            c.Measure(新しいサイズ(300, 300));
            c.Arrange(new Rect(0, 0, 300, 300));
        }
    }
}

注: 以下の最初の回答は、WPF アプリケーションのボタン クリック イベント中に同じ動作が発生することを既に明示的に述べているため、少し根拠がありません。ただし、そのアプリでは限られた回数 (たとえば 1000 回) の反復のみを行うことを明示的に述べていませんでした。そのようにすると、アプリケーションをクリックしたときに GC を実行できます。また、メモリ ダンプを取得したところ、オブジェクトが !gcroot を介してルート化されていることがわかったと明示的に述べたことにも注意してください。また、GC を実行できないことにも同意しません。コンソール アプリケーションのメイン スレッドで GC が実行されません。特に、コンカレント ワークステーション GC がアクティブであることを意味するデュアル コア マシンを使用しているためです。ただし、メッセージ ポンプはあります。

その点を証明するために、DispatcherTimer でテストを実行する WPF アプリケーション バージョンを次に示します。100 ミリ秒のタイマー間隔中に 1000 回の反復を実行します。ポンプからのメッセージを処理し、CPU 使用率を低く保つのに十分な時間。

システムを使用する;
System.Collections.Generic の使用;
System.Linq を使用します。
System.Text を使用します。
System.Windows を使用します。
System.Windows.Controls を使用します。
System.Windows.Shapes を使用します。

名前空間 SimpleReproSampleWpfApp
{
    パブリック部分クラス Window1 : ウィンドウ
    {
        プライベート System.Windows.Threading.DispatcherTimer _timer;

        public Window1()
        {
            InitializeComponent();

            _timer = 新しい System.Windows.Threading.DispatcherTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(100);
            _timer.Tick += new EventHandler(_timer_Tick);
            _timer.Start();
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        void RunTest()
        {
            for (int i = 0; i < 1000; i++)
            {
                BuildCanvas();
            }
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        private static void BuildCanvas()
        {
            Canvas c = new Canvas();

            行 line = new Line();
            line.X1 = 1;
            line.Y1 = 1;
            line.X2 = 100;
            line.Y2 = 100;
            線幅 = 100;
            c.Children.Add(行);

            c.Measure(新しいサイズ(300, 300));
            c.Arrange(new Rect(0, 0, 300, 300));
        }

        void _timer_Tick(オブジェクト送信者, EventArgs e)
        {
            _timer.Stop();

            RunTest();

            _timer.Start();
        }
    }
}

注2:最初の回答のコードを使用しましたが、メモリの成長が非常に遅かったです。1ms は私の例よりもはるかに遅く、反復回数が少ないことに注意してください。成長に気づき始める前に、数分間実行する必要があります。5 分後、30MB の開始点から 46MB になります。

注 3: .Arrange の呼び出しを削除すると、成長が完全になくなります。残念ながら、多くの場合、(RenderTargetBitmap クラスを介して) Canvas から PNG ファイルを作成しているため、この呼び出しは私の使用にとって非常に重要です。.Arrange を呼び出さないと、キャンバスはまったくレイアウトされません。

4

4 に答える 4

10

ご提供いただいたコードを使用して問題を再現できました。Canvas オブジェクトは決して解放されないため、メモリは増え続けます。メモリ プロファイラーは、Dispatcher の ContextLayoutManager がそれらすべてを保持していることを示します (必要に応じて OnRenderSizeChanged を呼び出せるようにするため)。

簡単な回避策は追加することです

c.UpdateLayout()

の最後までBuildCanvas

Canvasそうは言っても、それはUIElement;であることに注意してください。UIで使用することになっています。任意の描画面として使用するようには設計されていません。他のコメンテーターがすでに指摘しているように、何千もの Canvas オブジェクトの作成は、設計上の欠陥を示している可能性があります。プロダクション コードはもっと複雑かもしれませんが、キャンバスに単純な図形を描くだけなら、GDI+ ベースのコード (System.Drawing クラスなど) の方が適切かもしれません。

于 2008-10-11T02:17:13.450 に答える
2

.NET 3 および 3.5 の WPF には、内部メモリ リークがあります。特定の状況下でのみトリガーされます。何がそれを引き起こしているのかを正確に把握することはできませんでしたが、私たちのアプリにはそれがありました. どうやら.NET 4で修正されたようです。

このブログ記事に書いてあることと同じだと思います。

いずれにせよ、次のコードをApp.xaml.csコンストラクターに入れることで解決しました

public partial class App : Application
{
   public App() 
   { 
       new HwndSource(new HwndSourceParameters()); 
   } 
}

他に何も解決しない場合は、それを試して確認してください

于 2010-09-01T02:20:00.060 に答える
1

通常、.NET GCでは、特定のしきい値を超えるとオブジェクトの割り当てがトリガーされますが、メッセージポンプには依存しません(WPFとの違いは想像できません)。

Canvasオブジェクトはどういうわけか奥深くに根付いているのではないかと思います。BuildCanvasメソッドが終了する直前にc.Children.Clear()を実行すると、メモリの増加が劇的に遅くなります。

とにかく、ここでコメントしたように、フレームワーク要素のそのような使用法はかなり珍しいです。なぜこんなにたくさんのキャンバスが必要なのですか?

于 2008-10-10T18:35:40.527 に答える
0

編集2:明らかに答えではありませんが、ここでの回答とコメントの間のやり取りの一部だったので、削除しません。

ループとそのブロッキング呼び出しが終了しないため、GC はこれらのオブジェクトを収集する機会を得ることはありません。したがって、メッセージ ポンプとイベントは順番を取得しません。メッセージとイベントを実際に処理する機会を得るために何らかの種類のを使用した場合Timer、すべてのメモリを使い果たすことはおそらくないでしょう。

編集:間隔がゼロより大きい限り、以下は私のメモリを消費しません。間隔が 1 ティックであっても、0 でない限り。0 の場合は、無限ループに戻ります。

public partial class Window1 : Window {
    Class1 c;
    DispatcherTimer t;
    int count = 0;
    public Window1() {
        InitializeComponent();

        t = new DispatcherTimer();
        t.Interval = TimeSpan.FromMilliseconds( 1 );
        t.Tick += new EventHandler( t_Tick );
        t.Start();
    }

    void t_Tick( object sender, EventArgs e ) {
        count++;
        BuildCanvas();
    }

    private static void BuildCanvas() {
        Canvas c = new Canvas();

        Line line = new Line();
        line.X1 = 1;
        line.Y1 = 1;
        line.X2 = 100;
        line.Y2 = 100;
        line.Width = 100;
        c.Children.Add( line );

        c.Measure( new Size( 300, 300 ) );
        c.Arrange( new Rect( 0, 0, 300, 300 ) );
    }
}
于 2008-10-10T17:45:37.210 に答える