Domanda

Sto lavorando con un po 'di codice (non mio, non mi affretto ad aggiungere, non mi fido così tanto) di una classe che apre un socket, fa richieste e ascolta risposte, che sta gettando un'eccezione in un modo non riesco a capire quando testato in xunit. presumo si verifica la stessa eccezione " live " ma la classe è referenziata da un singleton, quindi probabilmente è semplicemente nascosta.

Il problema si manifesta come " System.CannotUnloadAppDomainException: errore durante lo scaricamento dell'appdomain " in xunit e l'eccezione interna è " System.ObjectDisposedException " lanciato (essenzialmente) all'interno del finalizzatore quando si chiude il socket! Non ci sono altri riferimenti al socket che chiama close e dispose è protetto nella classe Socket, quindi non sono chiaro come altrimenti l'oggetto potrebbe essere disposto.

Inoltre, se mi limito a catturare e assorbire ObjectDisposedException xunit termina quando colpisce la linea per chiudere il thread dell'ascoltatore.

Semplicemente non capisco come si possa disporre il Socket prima che gli venga chiesto di chiudere.

La mia conoscenza dei socket è solo ciò che ho imparato da quando ho riscontrato questo problema, quindi non so se ho fornito tutto ciò di cui potrebbe aver bisogno. LMK in caso contrario!

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;
            }
        }
    }
}
È stato utile?

Soluzione

Se questo oggetto è idoneo per la garbage collection e non ci sono altri riferimenti al Socket, il finalizzatore del socket potrebbe ben funzionare prima del finalizzatore del tuo oggetto. Ho il sospetto che sia quello che è successo qui.

In genere è una cattiva idea (IMO) fare questo lavoro in un finalizzatore. Non ricordo l'ultima volta che ho implementato un finalizzatore - se implementate IDisposable, dovreste andare bene a meno che non abbiate diretto riferimenti a risorse non gestite, che sono quasi sempre sotto forma di IntPtrs. Lo spegnimento ordinato dovrebbe essere la norma: un finalizzatore dovrebbe essere eseguito di solito solo se il programma si sta chiudendo o qualcuno ha dimenticato di eliminare l'istanza per iniziare.

(So che all'inizio hai chiarito che questo non è il tuo codice - ho solo pensato di spiegare perché è problematico. Mi scuso se hai già saputo qualcosa / tutto questo.)

Altri suggerimenti

A causa del modo in cui funzionano il garbage collector e i finalizzatori, i finalizzatori devono essere utilizzati solo se la tua classe è il diretto proprietario di una risorsa non gestita come un handle di finestra, un oggetto GDI, un handle globale o qualsiasi altro tipo di IntPtr.

Un finalizzatore non deve tentare di smaltire o addirittura utilizzare una risorsa gestita o rischierai di chiamare un oggetto finalizzato o eliminato.

Consiglio vivamente di leggere questo articolo Microsoft molto importante per maggiori dettagli su come funziona la garbage collection. Inoltre, questo è il riferimento MSDN su Implementare Finalize and Dispose to Clean Up Unmanaged Resources , cerca attentamente le raccomandazioni in basso.

In breve:

  • Se il tuo oggetto contiene una risorsa non gestita, devi implementare IDisposable e devi implementare Finalizer.
  • Se il tuo oggetto contiene un oggetto IDiposable, dovrebbe anche implementare IDisposable da solo e disporre quell'oggetto in modo esplicito.
  • Se il tuo oggetto contiene sia unmanaged che un usa e getta, il finalizzatore deve chiamare due diverse versioni di Dispose, una che rilascia usa e getta e non gestita, l'altra solo non gestita. Questo di solito viene fatto usando una funzione Dispose (bool) chiamata da Dipose () e Finalizer ().
  • Il Finalizzatore non deve mai utilizzare altre risorse oltre alla risorsa non gestita rilasciata e autonoma. In caso contrario si rischia di fare riferimento a oggetti raccolti o eliminati, dal momento che un oggetto viene temporaneamente riattivato prima della finalizzazione.

Nuove informazioni: sembra che io abbia effettivamente due problemi, ma il thread uno sembra essere piuttosto tossico.

Dal collegamento MSDN sopra:

  

" ThreadAbortException è uno speciale   eccezione che può essere catturata, ma   verrà automaticamente rialzato nuovamente a   la fine del blocco di cattura. "

Alcuni contenuti della comunità molto interessanti anche a quel link tra cui " Thread.Abort è un segno di un programma mal progettato " .

Quindi almeno ho delle munizioni per cambiarlo adesso :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top