2014-03-07 7 views
6

Ich habe einen .NET 4.5 WCF-Dienst mit asynchronen Operationen. Ich habe Integrationstests, die den Service-Host mithilfe von NetNamedPipeBinding erstellen und den Vorgang über einen Client ausführen.NUnit Async-Test verursacht AppDomainUnloadedException

jedoch jeder Test wie dies bewirkt, dass immer NUnit folgendes berichten:

System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
This can happen if the test(s) started a thread but did not stop it. 
Make sure that all the threads started by the test(s) are stopped before completion. 

Alles sieht ok für mich. Kann jemand sehen, was das verursacht? Ich habe ein komplettes Codeprobe auf GitHub: https://github.com/devlife/codesamples

+0

Welche Version von NUnit verwenden Sie? Alte Versionen werden natürlich nicht funktionieren. http://www.anthonysteele.co.uk/async-and-await-with-nunit –

+0

@LexLi Dies bezieht sich auf das Markieren von NUnit-Tests als async und das Aufrufen von asynchronen WCF-Vorgängen. Dies sind nur Methoden, die eine Aufgabe zurückgeben, auf der Sie '.Wait()' oder '.Result' einfach aufrufen können. –

+0

@PanagiotisKanavos Ich hoffe, Sie überprüfen seinen Code auf GitHub. –

Antwort

3

Ich habe das gleiche Problem. Es sieht so aus, als ob es sich bei dem Problem um "nachlässige" Completion-Port-Threads (im ThreadPool) handelt, die von WCF zur Verarbeitung von Async-IO verwendet wurden.

Wenn ServiceHost.Close() verwendet wird, signalisiert es alle diese Threads, die funktionieren, aber sie werden nicht sofort verschwinden, dh sie können das Ende der ServiceHost.Close() Operation überleben. Daher läuft das "Herunterfahren" -Verfahren mit der tatsächlichen Entladung von AppDomain, die durch NUnit verursacht wurde, aufgrund des Endes des Testlaufs.

Grundsätzlich ist eine einfache Thread.Sleep(<a couple of seconds>) nach ServiceHost.Close() „fixes“ dieses :-)

Nach langer Suche um im Internet ich nicht eine robuste Lösung für dieses Problem (für eine Auswahl an ähnlichen Problemen finden konnte, nicht alle aus dem gleichen Grund, obwohl google "Einheitstest appdomainunloadedexception", kurz, einen Weg, diese Warnung selbst zu unterdrücken.

Ich versuchte verschiedene Bindungen und Transporte (einschließlich der NullTransport), aber ohne Erfolg.

Am Ende habe ich mit dieser „Lösung“ besiedelt:

static void PreventPrematureAppDomainUnloadHack() 
{ 
    // 
    // When NUnit unloads the test AppDomain, the WCF started IO completion port threads might 
    // not have exited yet. 
    // That leads to AppDomainUnloadedExceptions being raised after all is said and done. 
    // While native NUnit, ReSharper oder TestDriven.NET runners don't show these, VSTest (and 
    // TFS-Build) does. Resulting in very annoying noise in the form of build/test warnings. 
    // 
    // The following code _attempts_ to wait for all completion port threads to end. This is not 
    // an exact thing one can do, however we mitigate the risk of going wrong by several factors: 
    // (1) This code is only used during Unit-Tests and not for production code. 
    // (2) It is only called when the AppDomain in question is about to go away anway. 
    //  So the risk of someone starting new IO threads while we're waiting is very 
    //  low. 
    // (3) Finally, we have a timeout in place so that we don't wait forever if something 
    //  goes wrong. 
    // 
    if (AppDomain.CurrentDomain.FriendlyName.StartsWith("test-domain-", StringComparison.Ordinal)) 
    { 
     Console.WriteLine("AppDomainUnloadHack: enabled (use DbgView.exe for details)."); 
     Trace.WriteLine(string.Format("AppDomainUnloadHack: enabled for domain '{0}'.", AppDomain.CurrentDomain.FriendlyName)); 

     AppDomain.CurrentDomain.DomainUnload += (sender, args) => 
     { 
      int activeIo; 
      var sw = Stopwatch.StartNew(); 
      var timeout = TimeSpan.FromSeconds(3); 

      do 
      { 
       if (sw.Elapsed > timeout) 
       { 
        Trace.WriteLine("AppDomainUnloadHack: timeout waiting for threads to complete."); 
        sw.Stop(); 
        break; 
       } 

       Thread.Sleep(5); 

       int maxWorkers; 
       int availWorkers; 
       int maxIo; 
       int availIo; 
       ThreadPool.GetMaxThreads(out maxWorkers, out maxIo); 
       ThreadPool.GetAvailableThreads(out availWorkers, out availIo); 
       activeIo = maxIo - availIo; 

       Trace.WriteLine(string.Format("AppDomainUnloadHack: active completion port threads: {0}", activeIo)); 

      } while (activeIo > 0); 

      Trace.WriteLine(string.Format("AppDomainUnloadHack: complete after {0}", sw.Elapsed)); 
     }; 
    } 
} 

Die Timeout 3 Sekunden ist völlig willkürlich, und so ist die Wartezeit von 5 ms zwischen jeder Wiederholungs. Manchmal bekomme ich ein "Timeout", aber die meiste Zeit funktioniert es.

Ich stelle sicher, dass dieser Code einmal für jede Test-Assembly aufgerufen wird (d. H. Durch einen statischen ctor eines referenzierten Typs).

Wie in solchen Fällen üblich YMMV.