我使用连续运行的C#在.NET 2中创建了一个Windows Forms应用程序。对于大多数帐户我很满意,但据报道,它偶尔会失败。我能够在50%的时间内监控其性能,我从未注意到失败。
此时我担心程序可能使用了太多资源,并且在不再需要时不会处理资源。
正确处理已创建计时器和图形路径,SQL连接等图形对象的已创建对象的最佳做法是什么?或者我可以依靠dispose方法来处理所有垃圾回收吗?
此外: 有没有办法监控应用程序使用的资源?
答案 0 :(得分:14)
最佳做法是确保实现IDisposable接口的所有对象在不再需要对象时立即调用Dispose on。
这可以使用using关键字或try/finally结构来完成。
在具有为表单生命周期分配资源的WinForms表单中,需要采用稍微不同的方法。由于表单本身实现了IDisposable,这表明在某个时间点将在此表单上调用Dispose。您希望确保您的可支配资源同时处理。为此,您应该覆盖表单Dispose(bool disposing)方法。实现应该如下所示:
protected override void Dispose(bool disposing)
{
if (disposing)
{
// dispose managed resources here
}
// dispose unmanaged resources here
}
关于表单中组件的说明:如果对象实现了IComponent接口,则可以将表单放在表单Container中。当容器本身被丢弃时,容器将负责处理组件。
答案 1 :(得分:4)
除了已经说过的内容之外,如果你正在使用COM组件,那么确保它们完全发布是一个非常好的主意。我有一个我一直用于COM版本的片段:
private void ReleaseCOMObject(object o)
{
Int32 countDown = 1;
while(countDown > 0)
countDown = System.Runtime.InteropServices.Marshal.ReleaseCOMObject(o);
}
答案 2 :(得分:2)
您应该拨打稀有资源上的Dispose
来释放它们。您可以使用using
语句:
using (var resource = new MyScarceObject())
{
// resource will be used here...
} // will free up the resources by calling Dispose automatically
答案 3 :(得分:2)
有几种方法可以确保这一点。我找到的主要帮助是使用“using”关键字。这是这样应用的:
using(SqlConnection connection = new SqlConnection(myConnectionString))
{
/* utilise the connection here */
}
这基本上转化为:
SqlConnection connection = null;
try
{
connection = new SqlConnection(myConnectionString);
}
finally
{
if(connection != null) connection.Dispose();
}
因此它只适用于实现IDisposable的类型。
在处理笔和画笔等GDI对象时,此关键字非常有用。但是,在某些情况下,您需要在更长的时间内保留资源,而不仅仅是方法的当前范围。作为一项规则,最好尽可能避免这种情况,但是例如在处理SqlCe时,保持与db连接打开的一个连接的性能更高。因此,人们无法摆脱这种需要。
在这种情况下,您无法使用“使用”,但您仍希望能够轻松回收连接所拥有的资源。 您可以使用两种机制来恢复这些资源。
一个是通过终结者。超出范围的所有托管对象最终都由垃圾收集器收集。如果您已经定义了终结者,那么GC将在收集对象时调用它。
public class MyClassThatHoldsResources
{
private Brush myBrush;
// this is a finaliser
~MyClassThatHoldsResources()
{
if(myBrush != null) myBrush.Dispose();
}
}
然而上面的代码很遗憾。原因是,在最终确定时,您无法保证已经收集了哪些管理对象,哪些尚未收集。以上示例中的“myBrush”可能已被垃圾收集器丢弃。因此,最好使用finaliser来收集托管对象,其用途是整理非托管资源。
终结者的另一个问题是它不具有确定性。让我们说例如我有一个通过串口通信的类。一次只能打开一个到串口的连接。因此,如果我有以下课程:
class MySerialPortAccessor
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
~MySerialPortAccessor()
{
if(m_Port != null) m_Port.Dispose();
}
}
然后,如果我使用这样的对象:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
private static void Test2()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
我会遇到问题。问题是终结者不是确定性的。也就是说我无法保证它何时运行,因此可以处理我的串口对象。因此,当我运行测试2时,我可能会发现端口仍处于打开状态。 虽然我可以在Test1()和Test2()之间调用GC.Collect()来解决这个问题但不推荐。如果你想从收集器中获得最佳性能,那就让它做自己的事情。
因此,我真正想做的是:
class MySerialPortAccessor : IDispable
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
public void Dispose()
{
if(m_Port != null) m_Port.Dispose();
}
}
我会像这样重写我的测试:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
private static void Test2()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
现在可以使用了。 那么终结者呢?为何使用它?
非托管资源以及不调用Dispose的可能实现。
作为其他人使用的组件库的作者;他们的代码可能忘记处理该对象。其他东西可能会杀死进程,因此不会发生.Dispose()。由于这些情况,应该实现终结器来清理任何非托管资源作为“最坏情况”场景,但是Dispose应该也整理这些资源,以便您进行“确定性清理”例程。
所以最后,.NET Framework Guidelines book中建议的模式是按如下方式实现:
public void SomeResourceHoggingClass, IDisposable
{
~SomeResourceHoggingClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
// virtual so a sub class can override it and add its own stuff
//
protected virtual void Dispose(bool deterministicDispose)
{
// we can tidy managed objects
if(deterministicDispose)
{
someManagedObject.Parent.Dispose();
someManagedObject.Dispose();
}
DisposeUnmanagedResources();
// if we've been disposed by .Dispose()
// then we can tell the GC that it doesn't
// need to finalise this object (which saves it some time)
//
GC.SuppressFinalize(this);
}
}
答案 4 :(得分:1)
一些提示:
- 尽可能利用Using()关键字。 如果您有单元测试,我建议您重构代码以实现此更改。
http://msdn.microsoft.com/en-us/library/yh598w02.aspx
- 请注意显式取消注册所有事件处理程序,并从应用程序整个持续时间内的列表中删除所有对象。这是程序员在.NET中最常见的错误,导致这些项目无法收集。
答案 5 :(得分:1)
至于监视内置perfmon(2)可以正常使用内存等。如果您担心文件句柄,DLL处理等,我建议Process Explorer和{ {3}}
答案 6 :(得分:1)
当对象无法访问时,Object.Finalize Method将被调用。在实现IDisposable的类中禁止这种不必要的调用很有帮助。您可以致电GC.SuppressFinalize Method
来完成此操作public void Dispose()
{
// dispose resources here
GC.SuppressFinalize(this);
}