2

私は、ソケットを開き、リクエストを作成し、応答をリッスンするクラスのいくつかのコード(急いで追加するのではなく、まったく信頼していません)で作業しています。これは、可能な方法で例外をスローしていますxunitでテストしたときに理解できません。同じ例外が「ライブ」で発生すると想定していますが、クラスはシングルトンによって参照されているため、おそらく非表示になっています

この問題は、xunit で「System.CannotUnloadAppDomainException: アプリケーション ドメインのアンロード中にエラーが発生しました」として明らかになり、内部例外は、ソケットを閉じるときにファイナライザー内で (本質的に) スローされる「System.ObjectDisposedException」です! close を呼び出して破棄するソケットへの他の参照は Socket クラスで保護されていないため、オブジェクトを他にどのように破棄できるかはわかりません。

さらに、単に ObjectDisposedException をキャッチして吸収すると、リスナー スレッドを閉じる行にヒットしたときに xunit が終了します。

ソケットを閉じるように求められる前に、ソケットを破棄する方法がわかりません。

ソケットに関する私の知識は、この問題を見つけてから学んだことだけなので、SO が必要とするすべてのものを提供したかどうかはわかりません。そうでなければLMK!

public class Foo
{
    private Socket sock = null;
    private Thread tListenerThread = null
    private bool bInitialised;
    private Object InitLock = null;
    private Object DeInitLock = null;

    public Foo()
    {
        bInitialised = false;

        InitLock = new Object();
        DeInitLock = new Object();
    }

    public bool initialise()
    {
        if (null == InitLock)
            return false;

        lock (InitLock)
        {
            if (bInitialised)
                return false;

            sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 8);
            sock.Bind( /*localIpEndPoint*/);
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(mcIP));

            tListenerThread = new Thread(new ThreadStart(listener));
            tListenerThread.Start();

            bInitialised = true;
            return true;
        }
    }

    ~Foo()
    {
        if (bInitialised)
            deInitialise();
    }

    private void deInitialise()
    {
        if (null == DeInitLock)
            return;

        lock (DeInitLock)
        {
            if (bInitialised)
            {
                sock.Shutdown(SocketShutdown.Both); //throws System.ObjectDisposedException
                sock.Close();

                tListenerThread.Abort(); //terminates xunit test!
                tListenerThread = null;

                sock = null;

                bInitialised = false;
            }
        }
    }
}
4

3 に答える 3

8

このオブジェクトがガベージ コレクションの対象であり、Socket への参照が他にない場合、ソケットのファイナライザーはオブジェクトのファイナライザーの前に実行される可能性があります。それがここで起こったことだと思います。

一般に、ファイナライザーでこれほど多くの作業を行うのはお勧めできません (IMO)。最後にファイナライザーを実装したのがいつだったかまったく思い出せません。IDisposable を実装する場合は、ほとんどの場合 IntPtrs の形式であるアンマネージ リソースへの直接参照がない限り、問題ありません。整然としたシャットダウンが標準であるべきです。通常、ファイナライザーは、プログラムがシャットダウンされているか、開始するインスタンスを破棄するのを誰かが忘れた場合にのみ実行する必要があります。

(最初に、これがあなたのコードではないことを明確にしたことは知っています-なぜそれが問題なのかを説明したいと思っただけです。すでにこれらの一部/すべてを知っていた場合はお詫びします。)

于 2009-01-08T10:29:23.007 に答える
4

ガベージコレクターとファイナライザーの動作方法により、ファイナライザーは、クラスがウィンドウハンドル、GDIオブジェクト、グローバルハンドル、その他の種類のIntPtrなどのアンマネージリソースの直接の所有者である場合にのみ使用する必要があります。

ファイナライザーは、管理対象リソースを破棄または使用しようとしてはなりません。そうしないと、ファイナライズまたは破棄されたオブジェクトを呼び出すリスクがあります。

ガベージコレクションの仕組みの詳細については、この非常に重要なMicrosoftの記事を読むことを強くお勧めします。また、これは、管理されていないリソースをクリーンアップするためのファイナライズと破棄の実装に関するMSDNリファレンスです。下部にある推奨事項を注意深く探してください。

一言で言えば:

  • オブジェクトがアンマネージリソースを保持している場合は、IDisposableを実装し、Finalizerを実装する必要があります。
  • オブジェクトがIDiposableオブジェクトを保持している場合は、それ自体でIDisposableも実装し、そのオブジェクトを明示的に破棄する必要があります。
  • オブジェクトがアンマネージドとディスポーザブルの両方を保持している場合、ファイナライザーは2つの異なるバージョンのDisposeを呼び出す必要があります。1つはディスポーザブルとアンマネージドをリリースし、もう1つはアンマネージドのみをリリースします。これは通常、Dipose()およびFinalizer()によって呼び出されるDispose(bool)関数を使用して行われます。
  • Finalizerは、解放されているアンマネージリソースと自分自身以外のリソースを使用してはなりません。そうしないと、オブジェクトがファイナライズの前に一時的に復活するため、収集または廃棄されたオブジェクトを参照するリスクがあります。
于 2009-01-08T13:50:27.487 に答える
0

新しい情報: これは実際には 2 つの問題を抱えているようですが、スレッド 1かなり有毒なようです。

上記の MSDN リンクから:

「ThreadAbortException はキャッチできる特別な例外ですが、キャッチ ブロックの最後で自動的に再度発生します。」

そのリンクには、「Thread.Abort は設計が不十分なプログラムの兆候です」などの非常に興味深いコミュニティ コンテンツもあります。

だから、少なくとも私はこれを今変更するための弾薬を持っています:)

于 2009-01-08T17:24:19.817 に答える