C# GC.KeepAlive以保留上下文
我有一个类,它是C# GC.KeepAlive以保留上下文,c#,.net,garbage-collection,using,idisposable,C#,.net,Garbage Collection,Using,Idisposable,我有一个类,它是WNetUseConnection 以下是一个实现(仅供参考): 下面是用法: using (var context = WindowsNetworking.CreateRemoteContext(storagePath, login, pass)) { // do something with storagePath GC.KeepAlive(context); } 问题是我是否应该编写GC.KeepAlive(context)?我的意思是直到我读了一篇文章(
WNetUseConnection
以下是一个实现(仅供参考):
下面是用法:
using (var context = WindowsNetworking.CreateRemoteContext(storagePath, login, pass))
{
// do something with storagePath
GC.KeepAlive(context);
}
问题是我是否应该编写GC.KeepAlive(context)
?我的意思是直到我读了一篇文章(关于AsyncLock
),我才写这样的代码,但现在我找不到链接,现在我不确定GC是否可以在这个方法完成之前调用终结器。理论上,它应该在使用
的最后
部分中使用处置
,但是这篇文章是由一个聪明人写的,所以我现在不确定
以防万一,我为引用的类提供代码:
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
您可以看到有一个隐藏的finally块,用于检查对象是否为null,然后对其调用Dispose。该引用将使对象在使用块的整个范围内保持活动状态
更新:请参阅,这个特定示例确实有机会提前调用终结器,因为我从未在dispose方法中使用任何使用隐式
this
的变量。为了保证行为,请确保使用实例级变量(我的简短示例中没有)或使用GC.SuppressFinalize(此)
未注释。GC.KeepAlive方法是。它所做的只是确保在代码中的该点读取特定变量,否则该变量将不会再次读取,因此不是保持对象活动的有效引用
这里没有意义,因为您传递给
KeepAlive
的同一变量将在稍后的时间点再次读取-在调用Dispose
时的隐藏finally
块期间。因此,GC.KeepAlive
在这里一事无成。我会说不。没有这篇文章,很难说这个“聪明人”到底提出了什么观点,以及你是否正确地解释了它。这只是从哪里来的历史。问题不是关于任何人,而是关于定稿。我可以说,“你不需要一个GC.KeepAlive
在这里”。我甚至可以把它贴出来作为一个答案,我自己也是一个相当聪明的人(而且也很谦虚!),我甚至可能是令人信服的。问题是,我不知道我反对的是什么观点,因为你不能链接到这篇文章,也不能说为什么你认为它可能适用于这种情况。如果你真的使用了WNetUseConnection(),这段代码的作用是很难猜测的。当你pinvoke任何东西时,没有明显的理由使用GC.KeepAlive()。@HansPassant感谢你的评论,TryConnectToRemote
实际上调用了WNetUseConnection
如果提供的路径是UNC,否则它什么也不做。我只是确信它与问题不太相关。一个聪明的GC/JIT组合实际上可能比显式的Dispose
更早完成这个对象-如果它可以跨多个方法进行优化,TestClass
中没有引用该类的任何字段,因此任何方法都不需要this
引用。与OPs类不同,OPs类访问Dispose
中的本地字段。所以这是一个观察,而不是一个保证。@Damien_不信者真的,我不知道JIT能走那么远。谢谢你的警告。我不知道是否有人会这么做,只是这是一种可能性。@ScottChamberlain规范至少会允许它这样做。从技术上讲,如果Dispose
不访问任何字段,或者使用隐式引用,这是不正确的(如果运行时可以证明Dispose
不需要对对象的引用,那么它可以在执行该代码之前回收该对象),但如果它引用了对象,那么这是完全正确的。如果Dispose
实际上没有利用对象实例,那么,从GC的目的来看,它不会延长对象的生命周期;GC.KeepAlive
将。因此在这一特定情况下,它是相关的。这是使用GC.suppressFinalize的原因之一e(这)
在Dispose
中,即使在没有终结器的对象上,也要确保任何具有终结器的嵌套对象在Dispose
完成之前保持活动状态。
After collection
In Dispose: True
After dispose, before 2nd collection
In finalizer
In Dispose: False
After 2nd collection
.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