GC.KeepAlive 保留上下文
GC.KeepAlive to preserve a context
我有一个 class,它是 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
,但现在我找不到一个link),现在我不确定GC是否可以调用一个此方法完成之前的终结器。理论上应该用using
的finally
段的Dispose
,不过这篇文章是聪明人写的,所以我现在不确定了。
为了以防万一,我提供了引用的代码 class:
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());
}
}
}
测试起来很容易,这里有一个快速测试程序,请确保它是 运行 在没有附加调试器的情况下处于发布模式。
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);
}
}
}
}
会一直输出
After collection
In Dispose: True
After dispose, before 2nd collection
In finalizer
In Dispose: False
After 2nd collection
更具体的证明,这里是上面程序的Main方法的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's comment below,这个具体示例确实有机会实际提前调用终结器,因为我从来没有使用任何变量在 dispose 方法中使用隐式 this
。为了保证该行为,请务必使用实例级变量(我的简短示例有 none)或取消注释 GC.SuppressFinalize(this);
。
GC.KeepAlive
方法是 empty。它所做的只是确保在代码中的那个点读取特定变量,否则该变量永远不会再次读取,因此不是保持对象存活的有效引用。
这里没有意义,因为您传递给 KeepAlive
的同一个变量是 在稍后的时间点再次读取 - 在隐藏 finally
在调用 Dispose
时阻塞。所以,GC.KeepAlive
在这里什么也做不了。
我有一个 class,它是 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
,但现在我找不到一个link),现在我不确定GC是否可以调用一个此方法完成之前的终结器。理论上应该用using
的finally
段的Dispose
,不过这篇文章是聪明人写的,所以我现在不确定了。
为了以防万一,我提供了引用的代码 class:
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());
}
}
}
测试起来很容易,这里有一个快速测试程序,请确保它是 运行 在没有附加调试器的情况下处于发布模式。
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);
}
}
}
}
会一直输出
After collection In Dispose: True After dispose, before 2nd collection In finalizer In Dispose: False After 2nd collection
更具体的证明,这里是上面程序的Main方法的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's comment below,这个具体示例确实有机会实际提前调用终结器,因为我从来没有使用任何变量在 dispose 方法中使用隐式 this
。为了保证该行为,请务必使用实例级变量(我的简短示例有 none)或取消注释 GC.SuppressFinalize(this);
。
GC.KeepAlive
方法是 empty。它所做的只是确保在代码中的那个点读取特定变量,否则该变量永远不会再次读取,因此不是保持对象存活的有效引用。
这里没有意义,因为您传递给 KeepAlive
的同一个变量是 在稍后的时间点再次读取 - 在隐藏 finally
在调用 Dispose
时阻塞。所以,GC.KeepAlive
在这里什么也做不了。