Com+和数据库访问对象(ADO.Net)的一些问题的研究(2002年12月10日)
Posted on 2004-10-09 17:38 Aaron@cnblogs 阅读(2466) 评论(2) 编辑 收藏 举报前言
翻到一篇以前写给老总的报告。现在看来有不少不妥之处。贴在这里算是备案。
原则:由问题引发,循序渐进,由表及里。
第一,简单回顾 Com+ 组件应具备的条件:
从 ServicedComponent 继承,有强名称,在 Com+ 环境中注册。可能需要拷贝到 GAC 区。
范例:
using System.EnterpriseSerives;
[assembly: AssemblyKeyFile("..\\..\\..\\MyComPlusSample.snk")]
[assembly:ApplicationActivation(ActivationOption.Server)]
// 不是必须的,可以在部署的时候进行设置。
[assembly: ApplicationName("MyFirstComPlus")]
namespace MyComPlusSample
{
public class MyFirstComPlus : ServicedComponent
{
...
}
}
问题一:是否需要释放 Com+ 中对象?如何释放 Com+ 中对象?何时释放 Com+ 中对象?
-- 是否需要释放 Com+ 中的对象?--
这个问题困扰了 OA 小组很久。这里特别是对于 Ado.Net 对象的
释放问题。我们开始的认识是 Ado.Net 应该是受托管的对象,所以
无需显式释放。但是在我们使用的时候发现凡第二次进行数据库操
作都会出现事务处理超时的错误。经过一轮跟踪发现走到ExecuteXXX
就停顿下来直至超时。后来仔细读微软的Enterprise Sample下的
FMStocks7 发现其中的 Dao 类在使用完 Ado.Net 对象后是调用
Dispose() 方法显式释放的。我把这个类部分代码摘抄,其内容如下:
public abstract class DAO : ServicedComponent
{
internal StoredProcedure sproc = null;
/// <summary>
/// This method disposes the sproc instance.
/// </summary>
override protected void Deactivate()
{
if (sproc != null)
{
sproc.Dispose();
sproc = null;
}
}
}
sealed internal class StoredProcedure : IDisposable
{
private SqlCommand command;
public void Dispose()
{
if (command != null)
{
SqlConnection connection = command.Connection;
command.Dispose();
command = null;
connection.Dispose();
}
}
// other operating ...
}
程序很简单。在 Dao 对象不处于活动状态下,就释放 Ado.Net 对象。
但是,为什么需要这样编写?还是没有一个准确的说法。在 msdn 里
也没有明确的说明。于是借助 ILDasm,我打开 Ado.Net 对象,比如
SqlConnection、SqlCommand 和对应的 Ole 对象来看。发现原因就在
这些类的构造器里。我把其中一个的部分还原为 C# 伪代码:
public SqlConnection()
{
...
GC.SuppressFinalize(this);
...
}
这句到底有什么用?为了方便讨论我钞了一段 Msdn 的说明:
<Msdn speciality begins>
[C#]
public static void SuppressFinalize(object obj);
该方法将 obj 从需要终止的对象集内移除。obj 参数应为此方法的调
用方。实现 IDisposable 接口的对象可以从 IDisposable.Dispose
方法调用此方法,以防止垃圾回收器对不需要终止的对象调用
Object.Finalize。
<Msdn speciality ends>
就是说,SqlConnection 对象不会被 GC (Garbage Collector) 自动回
收。我下面列了一下,有六个对象符合上面说的情况:
SqlConnection、OracleConnection 和 OleConnection
SqlCammond、OracleCammond 和 Oleammond
[讨论问题]
这里又产生一个问题:为什么在我们没有释放对象又没有使用事务的时候
可以正常工作?
-- 如何释放 Com+ 对象?--
我们一块来看看 Com+ 对象本身的释放方式。同样先看看 Msdn 上推荐的
Com+ 对象释放方式。 Com+ 提供了一个公开的静态方法来释放 Com+ 对象。
// 完成对象并移除关联的 COM+ 引用。
public static void DisposeObject(ServicedComponent sc);
但是在备注里却说:最好使用“处置”设计模式而非 DisposeObject。
我在另外一处又找到一段对此方法的说明:
/*
To return the object to the Object Pool use DisposeObject.
This will allow the Object to be reused from the pool.
If this call is not made, the garbage collector will not collect
this object and it won't be reused from the object pool.
*/
总结下来,似乎是在调用对象池的时候,应该用 DisposeObject,而
一般的情况下应该使用“标准”的方法(下面会有一个介绍)。忍不住我
又用 ILDasm 查找 ServicedComponent 的实现方式。下面是部分的伪代码:
public abstract ServicedComponent : ContextBoundObject, IDisposable, ...
{
...
public void Dispose()
{
ServicedComponent.DisposeObject(this); // 只是简单的调用静态
// 的 DisposeObject 方法。
}
}
相信现在问题都非常清楚了,我们实际上可以调用标准的 Dispose() 方法来释放
我们的 Com+ 对象。但是因为这没有在 Msdn 的正式文档中出现,难保以后不会
更改。所以,这个到 1.1.4322 还是正确的。继续往下讨论之前我们得看看微软推
荐标准的.Net 对象释放方式。为了讨论方便,摘抄下来:
// Implement Idisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// Take yourself off of the Finalization queue
// to prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
Components.Dispose();
}
// Release unmanaged resources. If disposing is false,
// only the following code is executed.
CloseHandle(handle);
handle = IntPtr.Zero;
// Note that this is not thread safe.
// Another thread could start disposing the object
// after the managed resources are disposed,
// but before the disposed flag is set to true.
}
disposed = true;
}
因为这段代码很简单,解释也很详细。Com+ 的基石 ServicedComponent 也是
采用同样的技术。在这里有个语法要稍微提一下的就是关于接口的实现的细节。
上次田轶生也介绍过接口方面的编程。先看一个模拟ServicedComponent 实现
方式的例子。
using System;
namespace DisposeTesing
{
public class MyServicedComponent : IDisposable
{
public MyServicedComponent()
{
Console.WriteLine("new My Serviced Component Object...");
}
public void Dispose()
{
Console.WriteLine("MyServicedComponent.Dispose()");
Console.WriteLine("Calling virtual Dispose method of MyServicedComponent ...");
this.Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
Console.WriteLine("MyServicedComponent.Dispose(bool disposing)");
}
}
// Com+ 组件释放对象一种可能的方式。
public class MyComPlus : MyServicedComponent
{
public MyComPlus()
{
Console.WriteLine("new My Com Plus Object...");
}
public void Dispose()
{
Console.WriteLine("MyComPlus.Dispose()");
Console.WriteLine("Calling virtual Dispose method of MyComPlus ...");
this.Dispose(true);
}
protected override void Dispose(bool disposing)
{
Console.WriteLine("MyComPlus.Dispose(bool disposing)");
Console.WriteLine("Calling base.Dispose(bool disposing)...");
base.Dispose(disposing);
}
}
public class Testing
{
public static void DisposeTest(IDisposable obj)
{
obj.Dispose();
}
public static void Main(string[] args)
{
MyComPlus myCom = new MyComPlus();
Console.WriteLine("\nCalling DisposeTest(myCom)...");
DisposeTest(myCom);
Console.WriteLine("\nCalling myCom.Dispose()...");
myCom.Dispose();
// pause
Console.Read();
}
}
}
别看例子有点长,但简单的说,第一个实现接口方法的类可以阻止它的继承类通过
override 来改变基类对此方法的实现,借以控制子类的某些行为。大家都看到了
在 C# 中很容易就能做到。子类只能通过 new 关键字来隐藏父类的实现方法,但
子类的方法无法通过接口按虚拟机制来调用。
有了 Msdn 上的推荐释放对象方式和对 ServicedComponent 工作方式的了解。我们就
可以确定,如果有 COM+ 中有需要释放的对象,就应该重写 ServicedComponent
的 Dispose(bool disposing) 方法来最终释放内部成员对象。在 Com+ 对象处于非活动
期之前就应该释放紧俏的系统资源,如 Ado.Net 对象、打开的磁盘文件等。
在释放成员之后都置为 null,以免重复释放。
[讨论]
其它语言不知道有没有这种特性。
[讨论2]
静态成员的生存期。在读 Ado.Net 的原码时,发现新版的 Dll 减少了对 static 类型的
使用。
[讨论?]
何时释放 Com+ 对象?根据具体的应用情况(与何种技术一起使用)。
Object Pooling 对象池
对想池的使用场合:需要简单的控制同步问题。像我们在上一个例子里看到的,会有一个
对象的两个实例同时对同一个资源进行访问。如果这不是我们需要的结果,就要加以控制。
当然,方法很多,可以用 Lock,或者其他传统的同步方法来控制,不过 Com+ 本身提供
一整套方案。当然解决同步问题不是 object pooling 主要任务,这里仅仅是一个提供
多一种选择。
要支持对象池,需要使用 ObjectPooling 属性类和JustInTimeActivation 属性类。
还要重载CanBePooled() 方法,在对象需要放回对象池的时候返回 true。
让看一个例子。
看一个没有使用的Object Pooling 的例子用来对比,看看是否存在同步问题。这个例子
使用了 Com+ 技术中的 CRM 事务补偿器。它主要用来处理本身不支持事务处理的而需要
事务处理的情况。比如比较典型的例子就是写磁盘文件。[看例子]
Com+ 推荐编写方法。
Com+ 的功能非常强大可以和各种技术组合针对不同的情况进行取舍,便于编写适应各种情
况的 Com+ 组件。单独的 讨论 Com+ 使用的标准非常很难提供一套完善的标准。如果是针
对我们已有项目使用情况,还是可以提出一种可行(但不一定是最优)的原则。
针对直接操作数据库的 Com+ 组件:
01。必须是强名称的程序集。
02。从 ServicedComponent 继承。
03。重写 Activate 方法,初始化数据库对象或者磁盘文件对象。
重写 Deactivate 方法,释放数据库对象或者磁盘文件对象。
即使组件没有用到对象池,也推荐用这种方法。
04。不要在构造器中使用 new 等构造新对象的方法,以保证 Com+ 对象(大多数情况下)总
是可以构建。而重写 Construct(string s) 方法,并初始化在此组件生命周期中只需初
始化一次的对象或代码。
05。重写 Dispose(bool disposing) 方法,在组件被释放之前一定要释放的资源。
06。编写析构器作为资源释放的最后保障。不是必须的,但推荐。它保证在
Dispose(bool disposing) 没有被调用的情况下依然能释放重要的资源。
07。对于必须控制访问数量或者必须独占的资源,可以考虑使用对象池技术。比如,磁盘文件
的读写、有限个数的数据库连接和硬件端口的操作。[这里仅仅是考虑,不是推荐。因为
我还不了解 .Net 到底是怎样实现多线程和多进程的。无法将对象池和 .Net 提供的同步
解决方案进行比较。虽然 .Net 提供的这些技术表面上看起来和原来的一样。但其行为很
可能与传统的不一致。而这些问题和具体的应用关联很大]。
08。如果使用对象池,就必须在对象需要放回对象池时在 CanBePooled() 方法中返回 true。
此方法会在 Deactivate() 方法之后对象被真正放回对象池之前调用。即,根据具体的应
用情况某些对象的释放也可以放在此处。
09。在遵循以上原则,而又没有一套完整的异常方案时,可以完全不用 Catch 异常。因为在出
现异常时 Deactivate()、CanBePooled()、Dispose(bool disposing) 和 Destructor 足
以及时和准确的释放资源。除非虚拟机或者整个操作系统崩溃。
10。本推荐的局限性。没有考虑 Com+ 技术提供的安全方案;没有充分考虑同步问题;以及其他
推荐内没有提及的内容。
对应的调用原则:
与一般的本地调用没有区别。只要遵循一般的调用原则即可。举例:
ComPlusObj obj = null;
try
{
obj = new ComPlusObj();
obj.Method();
}
catch(Exception ex)
{
// ...
}
finally
{
if(obj != null)
{
ServicedCompnonet.DisposeObject(obj);
// or Obj.Dispose();
}
}
安装部署
如果 M 的已声明可访问性为 protected internal,那么 M 的可访问域就是 T 的可访问域
与 P 的程序文本和从 T 派生的(在 P 之外声明的)任何类型的程序文本之间的交集。