GC.KeepAlive以保留上下文

本文关键字:上下文 保留 KeepAlive GC | 更新日期: 2023-09-27 17:58:36

我有一个类,它是WNetUseConnection 的简单包装器

以下是一个实现(仅供参考):

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);
    }
}

这里是用法:

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

问题是我是否应该写GC.KeepAlive(context)?我的意思是,直到我读了一篇文章(关于AsyncLock,但现在我找不到链接),我才写这样的代码,现在我不确定GC是否能在这个方法完成之前调用终结器。理论上,它应该在usingfinally部分使用Dispose,但这篇文章是一个聪明人写的,所以我现在不确定。


以防万一,我提供了引用类的代码:

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());
        }
    }
}

GC.KeepAlive以保留上下文

GC.KeepAlive方法为空。它所做的只是确保在代码中的那个点读取特定的变量,因为否则该变量将永远不会再被读取,因此不是保持对象活动的有效引用。

这里没有意义,因为传递给KeepAlive的同一变量将在稍后的时间点(调用Dispose时的隐藏finally块期间)再次读取。因此,GC.KeepAlive在这里什么都没有实现。

测试非常容易,这里有一个快速测试程序,请确保它在没有调试器的情况下以发布模式运行。

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);
            }
        }
    }
}

它将始终输出

收集后在Dispose中:True处置后,第二次收集前在终结器中在Dispose中:False第二次采集后

为了获得更具体的证明,以下是上述程序的主要方法的IL

.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

你可以看到有一个隐藏的finally块,它检查对象是否为null,然后对其调用Dispose。该引用将使对象在using块的整个范围内保持活动状态。

更新:请参阅下面Damien的评论,这个特定的示例确实有机会提前调用终结器,因为我从未在dispose方法中使用过任何使用隐式this的变量。为了保证行为,请确保使用实例级变量(我的简短示例中没有)或取消注释GC.SuppressFinalize(this);