2016-07-26 34 views
4

Ich habe eine Klasse, die für WNetUseConnectionGC.KeepAlive zu bewahren einen Kontext

Hier eine Implementierung (nur als Referenz) ist eine einfache Hülle ist:

internal class RemoteFileSystemContext : IDisposable 
{ 
    private readonly string _remoteUnc; 
    private bool _isConnected; 
    public RemoteFileSystemContext(string remoteUnc, string username, string password, bool promptUser) 
    { 
     if (WindowsNetworking.TryConnectToRemote(remoteUnc, username, password, promptUser)) 
     { 
      _isConnected = true; 
      _remoteUnc = remoteUnc; 
     } 
     else 
     { 
      GC.SuppressFinalize(this); 
     } 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
    } 

    ~RemoteFileSystemContext() 
    { 
     Dispose(false); 
    } 

    private void Dispose(bool isDisposing) 
    { 
     if (!_isConnected) 
      return; 
     _isConnected = false; 
     if (isDisposing) 
     { 
      GC.SuppressFinalize(this); 
     } 
     WindowsNetworking.DisconnectRemote(_remoteUnc); 
    } 
} 

und hier ist Nutzung:

using (var context = WindowsNetworking.CreateRemoteContext(storagePath, login, pass)) 
{ 
    // do something with storagePath 
    GC.KeepAlive(context); 
} 

Die Frage ist, ob ich GC.KeepAlive(context) schreiben soll oder nicht? Ich meine, ich habe keinen solchen Code geschrieben, bis ich einen Artikel gelesen habe (etwa AsyncLock, aber jetzt kann ich keinen Link finden), und jetzt bin ich mir nicht sicher, ob GC einen Finalizer aufrufen kann, bevor diese Methode beendet ist. Theoretisch sollte es Dispose in finally Abschnitt von using verwenden, aber dieser Artikel wurde von einem intelligenten Kerl geschrieben, also bin ich jetzt nicht sicher.


Nur für den Fall, bietet ich Code für referenzierte Klasse:

public static class WindowsNetworking 
{ 
    public static bool TryConnectToRemote(string remoteUnc, string username, string password, bool promptUser = false) 
    { 
     bool isUnc = remoteUnc != null && remoteUnc.Length >= 2 && remoteUnc[0] == '\\' && remoteUnc[1] == '\\'; 
     if (!isUnc) 
     { 
      return false; 
     } 
     ConnectToRemote(remoteUnc, username, password, promptUser); 
     return true; 
    } 

    public static IDisposable CreateRemoteContext(string remoteUnc, string username, string password, bool promptUser = false) 
    { 
     return new RemoteFileSystemContext(remoteUnc, username, password, promptUser); 
    } 

    public static void DisconnectRemote(string remoteUNC) 
    { 
     var ret = (NetworkError) WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false); 
     if (ret != NetworkError.NO_ERROR) 
     { 
      throw new Win32Exception((int) ret, ret.ToString()); 
     } 
    } 

    [DllImport("Mpr.dll")] 
    private static extern int WNetUseConnection(
     IntPtr hwndOwner, 
     NETRESOURCE lpNetResource, 
     string lpPassword, 
     string lpUserID, 
     int dwFlags, 
     string lpAccessName, 
     string lpBufferSize, 
     string lpResult 
     ); 

    [DllImport("Mpr.dll")] 
    private static extern int WNetCancelConnection2(
     string lpName, 
     int dwFlags, 
     bool fForce 
     ); 

    [StructLayout(LayoutKind.Sequential)] 
    private class NETRESOURCE 
    { 
     public int dwScope = 0; 
     public int dwType = 0; 
     public int dwDisplayType = 0; 
     public int dwUsage = 0; 
     public string lpLocalName = ""; 
     public string lpRemoteName = ""; 
     public string lpComment = ""; 
     public string lpProvider = ""; 
    } 

    private static void ConnectToRemote(string remoteUNC, string username, string password, bool promptUser) 
    { 
     NETRESOURCE nr = new NETRESOURCE 
     { 
      dwType = RESOURCETYPE_DISK, 
      lpRemoteName = remoteUNC 
     }; 

     NetworkError ret; 
     if (promptUser) 
      ret = (NetworkError) WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null); 
     else 
      ret = (NetworkError) WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null); 

     if (ret != NetworkError.NO_ERROR) 
     { 
      throw new Win32Exception((int) ret, ret.ToString()); 
     } 
    } 
} 
+0

Ich würde nein sagen. Ohne den Artikel ist es schwierig zu sagen, welchen Punkt dieser "schlaue Typ" eigentlich gemacht hat und ob Sie ihn richtig interpretiert haben. –

+0

Es ist nur eine Geschichte für "von wo es kommt". Frage nicht über irgendjemanden, sondern über Finalisierung. –

+2

Ich kann sagen, "Sie brauchen hier kein' GC.KeepAlive' ". Ich könnte es sogar als Antwort posten, ich bin selbst ein vernünftiger Klugscheißer (und auch bescheiden!), Und vielleicht bin ich sogar überzeugend.Das Problem ist, ich weiß nicht, welchen Punkt ich gegen * argumentiere, weil Sie nicht zu diesem Artikel verlinken oder sagen können, warum Sie glauben, dass es * in diesem Kontext * gelten kann. –

Antwort

4

Die GC.KeepAlive Methode ist empty. Es stellt lediglich sicher, dass eine bestimmte Variable an diesem Punkt im Code gelesen wird, da andernfalls diese Variable nie wieder gelesen wird und somit keine gültige Referenz ist, um ein Objekt am Leben zu erhalten.

Es ist hier sinnlos, weil die gleiche Variable, die Sie KeepAlive vorbei sind ist Lesen aus wieder zu einem späteren Zeitpunkt - während der versteckten finally Block, wenn Dispose genannt wird. Also, die GC.KeepAlive erreicht hier nichts.

+1

Wenn "Dispose" nicht auf Felder zugreift oder die implizite Referenz anderweitig verwendet, trifft das technisch nicht zu (Wenn die Laufzeitumgebung nachweisen kann, dass "Dispose" den Verweis auf das Objekt nicht benötigt, kann sie es zurückfordern vor dem Ausführen dieses Codes), aber wenn es auf das Objekt verweist, dann ist dies genau richtig. – Servy

+1

Wenn 'Dispose' die Objektinstanz nicht wirksam nutzt, verlängert es die Lebensdauer des Objekts aus den Zwecken des GC nicht; 'GC.KeepAlive' wird. In diesem speziellen Fall ist das relevant. – Servy

+0

Dies ist IMHO ein Grund dafür, 'GC.SuppressFinalize (this)' innerhalb von 'Dispose' zu haben, auch auf Objekten, die keine Finalizer haben - um sicherzustellen, dass alle verschachtelten Objekte, die * Finalisten haben, bis 'Dispose' am Leben erhalten werden ist fertig. – supercat

0

Es ist recht einfach zu testen, hier ist ein schnelles Testprogramm, sollten Sie es ohne einen Debugger im Release-Modus ausgeführt wird angebracht.

using System; 

namespace SandboxConsole 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      using (var context = new TestClass()) 
      { 

       GC.Collect(); 
       GC.WaitForPendingFinalizers(); 
       GC.Collect(); 

       Console.WriteLine("After collection"); 
      } 
      Console.WriteLine("After dispose, before 2nd collection"); 

      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
      GC.Collect(); 

      Console.WriteLine("After 2nd collection"); 
      Console.ReadLine(); 
     } 
    } 

    internal class TestClass : IDisposable 
    { 
     public void Dispose() 
     { 
      Dispose(true); 
     } 

     ~TestClass() 
     { 
      Console.WriteLine("In finalizer"); 
      Dispose(false); 
     } 

     private void Dispose(bool isDisposing) 
     { 
      Console.WriteLine("In Dispose: {0}", isDisposing); 
      if (isDisposing) 
      { 
       //uncomment this line out to have the finalizer never run 
       //GC.SuppressFinalize(this); 
      } 
     } 
    } 
} 

Es wird immer ausgegeben

 
After collection 
In Dispose: True 
After dispose, before 2nd collection 
In finalizer 
In Dispose: False 
After 2nd collection 

Weitere konkrete Beweis, hier ist die IL für die Hauptmethode des obigen Programms

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  85 (0x55) 
    .maxstack 1 
    .locals init ([0] class SandboxConsole.TestClass context) 
    IL_0000: newobj  instance void SandboxConsole.TestClass::.ctor() 
    IL_0005: stloc.0 
    .try 
    { 
    IL_0006: call  void [mscorlib]System.GC::Collect() 
    IL_000b: call  void [mscorlib]System.GC::WaitForPendingFinalizers() 
    IL_0010: call  void [mscorlib]System.GC::Collect() 
    IL_0015: ldstr  "After collection" 
    IL_001a: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_001f: leave.s IL_002b 
    } // end .try 
    finally 
    { 
    IL_0021: ldloc.0 
    IL_0022: brfalse.s IL_002a 
    IL_0024: ldloc.0 
    IL_0025: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    IL_002a: endfinally 
    } // end handler 
    IL_002b: ldstr  "After dispose, before 2nd collection" 
    IL_0030: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_0035: call  void [mscorlib]System.GC::Collect() 
    IL_003a: call  void [mscorlib]System.GC::WaitForPendingFinalizers() 
    IL_003f: call  void [mscorlib]System.GC::Collect() 
    IL_0044: ldstr  "After 2nd collection" 
    IL_0049: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_004e: call  string [mscorlib]System.Console::ReadLine() 
    IL_0053: pop 
    IL_0054: ret 
} // end of method Program::Main 

können Sie sehen, dass es eine versteckte ist blockiere schließlich, ob das Objekt null ist, und rufe dann Dispose auf. Diese Referenz hält das Objekt am gesamten Umfang des verwendeten Blocks am Leben.

UPDATE: Siehe Damien's comment below dieses spezifische Beispiel hat die Möglichkeit hat, die Finalizerthread früh tatsächlich ruft aufgrund der Tatsache, dass ich eine impliziten this in der Dispose-Methode nicht immer alle Variablen verwenden, die verwendet werden. Um das Verhalten zu garantieren, sollten Sie eine Instanzvariable verwenden (die in meinem kurzen Beispiel keine enthält) oder GC.SuppressFinalize(this); unkommentiert haben.

+0

Gute grundlegende Codebeispiel, ich mag es! – BolletuH

+3

Ein cleverer GC/JIT-Combo * kann * dieses Objekt tatsächlich früher als das explizite 'Dispose'-Objekt finalisieren. Wenn es über mehrere Methoden hinweg optimiert werden kann, verweist nichts in Ihrer' TestClass' auf irgendwelche Felder der Klasse und so auf 'this 'Verweise werden von keiner der Methoden benötigt. Anders als die OPs-Klasse, die auf lokale Felder innerhalb des 'Dispose' zugreift. Das ist also eine Beobachtung, keine Garantie. –

+0

@Damien_The_Unbeliever Wirklich, ich wusste nicht, dass das JIT so weit gehen kann. Danke für das Wort der Warnung. –