23

最近、オブジェクトが破棄されないというトリッキーなバグを調査していました。

コードでいくつかのパターンを見つけました。一部の m_foo が破棄されていないことが報告されていますが、SomeClass のすべてのインスタンスが破棄されているようです。

public class SomeClass: IDisposable
{
    void Dispose()
    {
       if (m_foo != null)
       {
          m_foo.Dispose();
       }
       if (m_bar != null)
       {
          m_bar.Dispose();
       }   
    }

    private Foo m_foo;

    private Bar m_bar;

}

Foo.Dispose が例外をスローする可能性があるため、次のコードが実行されないため、m_bar が破棄されない可能性があると思われます。

Foo/Bar はサード パーティのものである可能性があるため、例外がスローされないことは保証されません。

すべての Dispose 呼び出しを try-catch でラップするだけでは、コードがぎこちなくなります。

これを処理するためのベストプラクティスは何ですか?

4

7 に答える 7

35

特にIDisposableを実装するものは通常、Disposeを呼び出すファイナライザーを指定するため、disposeメソッドの例外をリークするのはかなり悪いことです。

問題は、例外を処理してカーペットの下にある問題を一掃すると、デバッグが非常に困難な状況が発生する可能性があることです。IDisposable が、破棄後にのみ解放される種類のクリティカル セクションを割り当てたとしたらどうなるでしょうか。例外が発生したという事実を無視すると、デッドロック中央に陥る可能性があります。Dispose での失敗は、早期に失敗したいケースの 1 つであるべきだと思います。そのため、発見されたらすぐにバグを修正できます。

もちろん、それはすべて、破棄されるオブジェクトに依存します。回復できるオブジェクトもあれば、回復できないオブジェクトもあります。一般的な経験則として、Dispose は正しく使用された場合に例外をスローすべきではなく、呼び出しているネストされた Dispose メソッドの例外を防御的にコーディングする必要はありません。

じゅうたんの下に OutOfMemoryException を一掃したくありませんか?

Dispose で恣意的に例外をスローする危険なサード パーティ製コンポーネントがあった場合、それを修正し、別のプロセスでホストして、再生が開始されたときに破棄できるようにします。

于 2009-06-23T03:39:00.073 に答える
7

Dispose() がファイナライズ コンテキスト内で呼び出され、例外がスローされた場合、プロセスは終了します。

Foo.Dispose() が例外をスローしていると思われる場合は、可能であれば最後に破棄し、try/catch でラップします。キャッチでそれを取り除くためにできる限りのことを行います-参照をnullに設定します。Dispose() から例外をスローすることは非常に悪いことであり、避けるべきです。

残念ながら、これがバグのあるサードパーティのコードである場合は、サードパーティに修正してもらうのが最善の策です。その後、手動でクリーンアップする必要はありません。

それが役立つことを願っています。

于 2009-06-23T03:18:30.283 に答える
5

設計規則によると:

「IDisposable.Dispose メソッドは例外をスローすべきではありません。」

したがって、Dispose() からの未処理の例外が原因でプログラムがクラッシュする場合は、公式の解決策を参照してください

于 2016-03-04T18:18:35.067 に答える
2

using() ステートメントで変数を割り当てる必要がないため、これに「スタックされた」using ステートメントを使用しないのはなぜですか?

void Dispose()
{
    // the example in the question didn't use the full protected Dispose(bool) pattern
    // but most code should have if (!disposed) { if (disposing) { ...

    using (m_foo)
    using (m_bar)  
    {
        // no work, using statements will check null 
        // and call Dispose() on each object
    }

    m_bar = null;
    m_foo = null;
}

「積み重ねられた」using ステートメントは次のように展開されます。

using (m_foo)
{
    using (m_bar) { /* do nothing but call Dispose */ }
}

したがって、Dispose() 呼び出しは別の finally ブロックに配置されます。

try {
    try { // do nothing but call Dispose
    }
    finally { 
        if (m_bar != null)
            m_bar.Dispose(); 
    }
finally { 
    if (m_foo != null)
        m_foo.Dispose();
}

これに関する参照を 1 か所で見つけるのに苦労しました。「スタックされた」using ステートメントは、Joe Duffy の古いブログ投稿にあります (セクション「C# および VB の Using ステートメント、C++ スタック セマンティクス」を参照)。Joe Duffy の投稿は、IDisposable に関する多くの StackOverflow の回答で参照されています。また、ローカル変数の using ステートメントを積み重ねるのが一般的であるという最近の質問も見つけました。C# 言語仕様以外に、finally ブロックのチェーンが見つかりませんでした(C# 3.0 仕様のセクション 8.13)、単一の「using」ブロック内の複数の変数に対してのみ、これは私が提案しているものではありませんが、IL を逆アセンブルすると、try/finally ブロックがネストされていることがわかります. ヌル チェックでは、C# の仕様からも次のように述べられています。

于 2009-10-24T06:46:22.440 に答える
1

Dispose は、例外をスローすることは想定されていません。もしそうなら—それはよく書かれていないので…</p>

try { some.Dispose(); } catch {}

十分なはずです。

于 2010-10-20T07:18:53.433 に答える
0

私の場合、フォームを閉じているときに UI 要素にアクセスするスレッドが原因でした。フォームクローズ時にスレッドを中止することで解決しました。(「FormClosing」イベント)

FormClosing += (o, e) => worker.Abort();
于 2016-08-20T18:45:17.580 に答える
0

オブジェクトを破棄するためのコードの繰り返しを避けるために、次の静的メソッドを作成しました。

    public static void DisposeObject<T>(ref T objectToDispose) where T : class
    {
        IDisposable disposable = objectToDispose as IDisposable;
        if (disposable == null) return;

        disposable.Dispose();
        objectToDispose = null;
    }

重要な点は、関数にすることができるため、破棄するオブジェクトごとに 1 行だけ入力し、Dispose メソッドをきれいに保つことです。私たちの場合、破棄されたポインターを null にするのが慣習であったため、ref パラメーターが使用されました。

あなたの場合、例外処理を追加するか、例外処理で別のフレーバーを作成することができます。Dispose() が例外をスローするたびにログ/ブレークポイントを設定するようにしますが、それを防ぐことができない場合は、問題が広がらないようにすることをお勧めします。

于 2010-02-17T22:38:01.040 に答える