2

私は最初のMVVMアプリケーションを作成しました。アプリケーションを閉じると、ObjectDisposedExceptionによってクラッシュが発生することがよくあります。クラッシュは、アプリウィンドウが消えた直後に、アプリケーションが停止すると表示されます。

スタックトレースを取得するのは困難でしたが(他の質問を参照)、最終的にスタックトレースが完全にC#ライブラリ(kernel32!BaseThreadStart、mscorwks!Thread、mscorwks!WKSなど)に含まれていることがわかりました。

さらに、このクラッシュには一貫性がありません。私の最後のチェックアウトと再構築の後、それは起こりませんでした...しばらくの間。それから戻ってきました。それが起こり始めると、私が「クリーン」にして再構築しても、それは起こり続けます。ただし、ワイプアンドチェックアウトによってしばらく停止することがあります。

私が考えていること:

私のViewModelsを破棄するとき、GarbageCollectorは何か面白いことをしていると思います。私のViewModelBaseクラスデストラクタには、デストラクタが呼び出されたときにログに記録するWriteLineがあり、4つのViewModelのうち、2つまたは3つだけが破棄され、チェックアウトによって異なるようです(たとえば、私のもので実行すると、一貫して繰り返されますシーケンスですが、私の同僚は、異なるオブジェクトが配置された異なるシーケンスを見ています)。

スタックトレースには私のコードの呼び出しが含まれていないので、それは、破棄されたオブジェクトのメソッドを呼び出しているのは私のコードではないことを意味すると思います。そのため、CLRは馬鹿げていると思います。

これは意味がありますか?GCの一貫性を保つ方法はありますか?これは赤いニシンですか?

役立つ可能性のあるその他の詳細:
すべてのビューとViewModelは、App.xaml.csファイルのアプリケーションのStartupイベントハンドラーで作成されます。同じハンドラーがViewModelsをDataContextsに割り当てます。これが正しいMVVMプラクティスであるかどうかはわかりませんが(私が言ったように、私の最初のMVVMアプリ)、なぜそれが悪い動作を引き起こすのかわかりません。

必要に応じてコードを貼り付けることができます。

4

3 に答える 3

13

私の ViewModelBase クラスのデストラクタには、デストラクタが呼び出されたときにログに記録する WriteLine があります。

それは本当に悪いです。デバッグビルドでのみ有効にしていただければ幸いです。

ファイル ハンドルの作成、ディスクの状態の操作など、デストラクタで複雑なことを絶対に行うべきではありませんそれは、最悪のトラブルを要求しているだけです。デストラクタは、管理されていないリソースをクリーンアップし、他に何もしない必要があります。

私の 4 つの ViewModel のうち、破棄されるのは 2 つまたは 3 つだけで、チェックアウトによって異なるようです (たとえば、自分で実行すると、一貫して繰り返されるシーケンスが表示されますが、同僚は、異なるオブジェクトが破棄された別のシーケンスを表示します)。

以下で説明するように、さまざまなタイミングでさまざまな順序で物事が起こっているのは、完全に予想されることです。

デストラクタを正しく記述することは、C# で行うのが最も難しいことの 1 つです。プロセスがシャットダウンする前のファイナライズの最後のラウンドで例外が発生しているということは、おそらく間違っていることを示しています。

そのため、CLRはばかげていると思います。

エラーの原因をツールのせいにすることは、問題の解決に役立つ可能性はほとんどありません。

デストラクタを書く前に誰もが知っておくべきことは次のとおりです。

  • デストラクタは、必ずしも他のコードと同じスレッドで実行されるとは限りません。つまり、競合状態、ロックの順序付けの問題、弱いメモリ モデルが原因で読み取りと書き込みが時間内に移動する可能性があることを意味します。デストラクタを使用すると、自動的にマルチスレッド プログラムが作成されるため、考えられるすべてのスレッドの問題を防ぐようにプログラムを設計する必要があります。それはあなたの責任であり、CLR の責任ではありません。スレッドセーフなオブジェクトを作成する責任を負いたくない場合は、デストラクタを作成しないでください。

  • オブジェクトが初期化されていない場合でも、デストラクタは実行されます。オブジェクトが割り当てられた後、コードがコンストラクターの途中で例外がスローされる可能性は十分にあります。オブジェクトは割り当てられていますが、ファイナライズを抑制していないため、破棄する必要があります。デストラクタは、不完全に初期化されたオブジェクトに直面しても堅牢である必要があります。

  • オブジェクトが一貫性のある変更を保証するためのロック下にあり、例外がスローされ、finally ブロックが一貫性のある状態を復元しない場合、オブジェクトはファイナライズ時に一貫性のない状態になります。デストラクタは、中止されたトランザクションの結果として矛盾する内部状態を持つオブジェクトに直面しても堅牢である必要があります。

  • デストラクタは任意の順序で実行できます。すべてが相互に参照し、同時に死んでいるオブジェクトのツリーがある場合、各オブジェクトのデストラクタはいつでも実行できます。デストラクタは、内部状態が破壊された、または破壊されていない他のオブジェクトを参照するオブジェクトに直面して堅牢でなければなりません。

  • ファイナライザー キューで破棄を待機しているオブジェクトは、ガベージ コレクターに従って生きています。デストラクタは、以前に死んでいたオブジェクトを一時的に (願わくば!) 再び生き返らせます。プログラム ロジックが、死んだオブジェクトが死んだままであることに依存している場合は、デストラクタに細心の注意を払う必要があります。(そして、デストラクタ ロジックによってオブジェクトが永続的に生き返った場合、大きな問題が発生する可能性があります。そうしないでください。)

  • 破棄を待っているオブジェクトは生きていて、GC がそれらを死んでいると分類したために破棄が必要であると識別されるため、ファイナライズを待っているオブジェクトは世代別ガベージ コレクターで自動的に 1 世代上に移動します。これは、ガベージ コレクターによるストレージの再利用が、オブジェクトが2回目の停止になるまで発生しないことを意味します。オブジェクトは後の世代に移動したばかりなので、それは長い間決定されない可能性があります。デストラクタによって、有効期間が短いメモリ割り当てが大幅に有効期間が長くなり、シナリオによってはガベージ コレクタのパフォーマンスに深刻な影響を与える可能性があります。大きくて寿命の短いオブジェクト (またはさらに悪いことに、数百万個を作成しようとしている小さな寿命の短いオブジェクト) のデストラクタを作成する前に、非常に慎重に検討してください。デストラクタを持つオブジェクトは、ファイナライズを明示的に抑制しない限り、ジェネレーション ゼロ コレクタによって解放できません

  • デストラクタが呼び出される保証はありません。ガベージ コレクターは、プロセスが停止していることがわかっている場合でも、プロセスがシャットダウンする前にオブジェクトのデストラクターを実行する必要はありません。あなたのロジックは、呼び出されたデストラクタに正確さを依存することはできません。たとえば、FailFast、スタック オーバーフロー例外、誰かが壁から電源コードを引き抜くなど、多くのことがデストラクタの呼び出しを妨げる可能性があります。プログラムは、呼び出されないデストラクタに直面しても堅牢である必要があります。

  • 未処理の例外をスローするデストラクタは、プロセスを危険な状態にします。ランタイム エンジンは、これが発生した場合にプロセス全体をフェイルファストにする権利を完全に有します。(必須ではありませんが。)デストラクタは未処理の例外をスローしてはなりません。

これらの制限を受け入れたくない場合は、最初からデストラクタを記述しないでください。あなたが好むと好まざるとにかかわらず、これらの制限はなくなりません。

于 2011-10-19T16:33:35.560 に答える
4

メイン アプリケーションの終了時に ViewModel の破棄に関するログ アクションが完了していないため、アプリケーションは例外をスローしています。

実際のファイル書き込みを実行するために、子プロセスが生成されることがわかります。メイン アプリケーションが終了するまでにこれが完了していない場合は、エラーが発生します。

このタイプのアクションを実行する場合は、メイン アプリケーションが終了する前に、子プロセス/スレッドプール スレッドなどが完了するまでしばらく待機する必要があります。

アプリケーションの終了中に発生するイベントを確実にログに記録したい場合は、ログ プロセス (ログ ファイルへの実際の書き込み) を、メッセージを投稿する別のプライマリ スレッドとして実行することをお勧めします。そうすれば、ロギング プロセスがディスクへの書き込みを完了する前に、アプリケーションを閉じることができます。

于 2011-10-19T16:32:56.940 に答える
2

私のViewModelsを破棄するとき、GarbageCollectorは何か面白いことをしていると思います。私のViewModelBaseクラスのデストラクタには、デストラクタが呼び出されたときにログに記録するWriteLineがあります

それはおそらくそこにある問題です。本当に正当な理由がない限り、ファイナライザーを使用するべきではありません。ログ記録は間違いなくそれらの1つではありません。

ファイナライザーは予測可能な順序で実行されないことを理解する必要があります。GCは、必要なときに必要な順序でファイナライザーを呼び出すことができます。これは、一見ランダムな例外動作が発生する理由をおそらく説明しています。

于 2011-10-19T16:34:05.090 に答える