9

ライブラリによって計算されたエンティティが常に有効なデータを持っているはずなのに、時折nullになるベンダーライブラリの利用に関していくつかの問題がありました。

機能しているコード (ベンダーとの問題をデバッグした後) は、おおよそ次のとおりです。

    Task.Factory.StartNew(() => ValidateCalibration(pelRectRaw2Ds, crspFeatures, Calibration.Raw2DFromPhys3Ds));

    .....

    private void ValidateCalibration(List<Rectangle> pelRectRaw2Ds, List<List<3DCrspFeaturesCollection>> crspFeatures, List<3DCameraCalibration> getRaw2DFromPhys3Ds)
    {
        var calibrationValidator = new 3DCameraCalibrationValidator();

        // This is required according to vendor otherwise validationResultsUsingRecomputedExtrinsics is occasionally null after preforming the validation
        GC.SuppressFinalize(calibrationValidator);

        3DCameraCalibrationValidationResult validationResultUsingOriginalCalibrations;
        3DCameraCalibrationValidationResult validationResultsUsingRecomputedExtrinsics;
        calibrationValidator.Execute(pelRectRaw2Ds, crspFeatures, getRaw2DFromPhys3Ds, out validationResultUsingOriginalCalibrations, out validationResultsUsingRecomputedExtrinsics);

        Calibration.CalibrationValidations.Add(new CalibrationValidation
            {
                Timestamp = DateTime.Now,
                UserName = Globals.InspectionSystemObject.CurrentUserName,
                ValidationResultUsingOriginalCalibrations = validationResultUsingOriginalCalibrations,
                ValidationResultsUsingRecomputedExtrinsics = validationResultsUsingRecomputedExtrinsics
            });
    }

検証プロセスはかなり時間のかかる操作なので、タスクに渡します。私が抱えていた問題は、もともと GC.SuppressFinalize(calibrationValidator) への呼び出しがなく、アプリケーションがリリース ビルドから実行されたときに、出力パラメーター validationResultsUsingRecomputedExtrinsics が null になることでした。アプリケーションをデバッグ ビルドから実行した場合 (デバッガーが接続されているかどうかに関係なく)、validationResultsUsingRecomputedExtrinsics には有効なデータが含まれます。

この状況で GC.SuppressFinalize() が何をしたか、または問題をどのように修正したかを完全には理解していません。GC.SuppressFinalize() に関して私が見つけることができるのは、IDisposable を実装するときに使用されることだけです。「標準」コードでの使用は見つかりません。

GC.SuppressFinalize(calibrationValidator) への呼び出しを追加すると、この問題がどのように/なぜ修正されるのですか?

ベンダー ライブラリの内部構造に関する詳細な知識がなければ、確実に知ることはできないかもしれないことは理解していますが、洞察は役に立ちます。

アプリケーションは VS2012 でコンパイルされ、.NET 4.0 を対象としています。そのベンダー ライブラリでは、app.config で useLegacyV2RuntimeActivationPolicy="true" オプションが指定されている必要があります。

これは、ベンダーから受け取った正当な理由です。

SuppressFinalize コマンドは、ガベージ コレクターが何かを「早期」にクリーンアップしないようにします。なんらかの理由で、アプリケーションがガベージコレクターに少し熱心になり、オブジェクトを完全に処理する前にオブジェクトをクリーンアップすることがあったようです。これはほぼ確実にスコープに関連しており、おそらくマルチスレッドが原因で、calibrationValidator のスコープに混乱が生じています。以下は、エンジニアリングから得た回答です。

変数はローカル スコープで作成され、その関数はバックグラウンド スレッドで実行されるため、ガベージ コレクションはメイン スレッドで実行され、ガベージ コレクションはマルチスレッドの状況を処理するのに十分スマートではないようです。場合によっては、リリースが早すぎることがあります (バリデーターの内部実行がまだ終了していないため、この変数が必要です)。

4

3 に答える 3

16

これはおそらく、時期尚早のガベージ コレクションの問題を解決するためのハックです。カメラ アプリケーションで一般的なアンマネージ コードでは珍しくありません。これは健全なハックではなく、ファイナライザーが実行されないため、リソース リークが発生する可能性が高くなります。アンマネージ コードのラッパーは、ほとんどの場合、ファイナライザーで何かを行う必要があります。アンマネージ メモリを解放する必要があるのは非常に一般的です。

問題は、アンマネージ コードの実行中に、calibrationValidator オブジェクトがガベージ コレクションされる可能性があることです。プログラムに別のスレッドがあると、そのスレッドがオブジェクトを割り当てて GC をトリガーできるため、これが起こりやすくなります。これは、コードの所有者がテスト中に見落としがちです。複数のスレッドを使用しているときにコードをテストしたことがなかったり、GC を間違ったタイミングでトリガーするほど幸運に恵まれなかったりするためです。

あなたの側での適切な修正は、ジッターが呼び出しを過ぎて使用中のオブジェクトをマークし、ガベージコレクターがオブジェクトを収集しないようにすることです。GC.KeepAlive(calibrationValidator)そのためには、呼び出しの後に追加しExecute()ます。

于 2013-03-22T03:24:42.577 に答える
3

IDisposableC# で、 、およびファイナライザーを理解GC.SuppressFinalizeする場合、次の記事よりも適切な説明はないと思います。

DG の更新: 破棄、ファイナライズ、およびリソース管理

大丈夫!これは、改訂された「廃棄、ファイナライズ、およびリソース管理」設計ガイドライン エントリです。この作品については、以前にここここで説明しました。印刷されたページが 25 ページまであり、マイナー アップデートとは見なされません。予想以上に時間がかかりましたが、結果には満足しています。私は HSutter、BrianGru、CBrumme、Jeff Richter、およびその他の数人の人々と協力して、かなりの量のフィードバックを受け取りました...とても楽しいです。

この質問の重要な概念:

それだけで呼び出されるべきであることは非常に明白であるため、記事ではそれについて直接言及することさえありません。ただし、外部コードがそれらのリソースを呼び出せないようにするために、ファイナライズ可能なオブジェクトをラップしてパブリック API から分離する方法について言及しています(次の引用を参照)。元の質問で説明されているライブラリを設計した人は誰でも、.NET でのファイナライズのしくみを把握していません。GC.SuppressFinalize()thisGC.SuppressFinalize()

ブログ記事より引用:

上記のまれな状況のいずれかが存在しない場合でも、公的にアクセス可能な参照を持つファイナライズ可能なオブジェクトは、任意の信頼できない呼び出し元によってそのファイナライズが抑制される可能性があります。具体的には、GC.SuppressFinalize を呼び出して、重要なファイナライズを含め、ファイナライズが完全に行われないようにすることができます。これに対処するための適切な緩和戦略は、重要なリソースを、ファイナライザーを持つ非パブリック インスタンスにラップすることです。これを呼び出し元に漏らさない限り、ファイナライズを抑制することはできません。クラスで SafeHandle を使用するように移行し、それをクラス外に公開しない場合は、リソースのファイナライズを保証できます (上記の注意事項と SafeHandle の正しい実装を前提としています)。

于 2013-03-22T01:52:39.273 に答える
1

マルチスレッドまたはネイティブ コードがこの問題の原因であるという言及がいくつかあります。しかし、純粋に管理されたほとんどシングルスレッドのプログラムでも同じことが起こります。

次のプログラムを検討してください。

using System;

class Program
{
    private static void Main()
    {
        var outer = new Outer();
        Console.WriteLine(outer.GetValue() == null);
    }
}

class Outer
{
    private Inner m_inner = new Inner();

    public object GetValue()
    {
        return m_inner.GetValue();
    }

    ~Outer()
    {
        m_inner.Dispose();
    }
}

class Inner
{
    private object m_value = new object();

    public object GetValue()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        return m_value;
    }

    public void Dispose()
    {
        m_value = null;
    }
}

ここで、outer.GetValue()が呼び出されている間に、outerガベージ コレクションが行われ、ファイナライズされます (少なくともリリース モードでは)。ファイナライザーは、Innerオブジェクトのフィールドを null にします。つまり、GetValue()が返されnullます。

GC実際のコードでは、ほとんどの場合、そこに呼び出しはありません。代わりに、(非決定論的に) ガベージ コレクターを実行させる管理対象オブジェクトを作成します。

(このコードはほとんどがシングル スレッドであると言いました。実際、ファイナライザーは別のスレッドで実行されますが、 への呼び出しによりWaitForPendingFinalizers()、まるでメイン スレッドで実行されているかのようになります。)

于 2013-03-22T18:50:08.270 に答える