我有一个配置为单例实例化的WCF服务器。我的客户端将参数传递给服务方法,一个实现IDisposable
接口的对象,服务器正在尝试缓存该实例,但是WCF运行时会在执行完服务后,会过早破坏我的缓存实例。
通过查看堆栈跟踪,我发现在Dispose()
方法内部正在调用参数的MessageRpc.DisposeParametersCore()
。
这是该方法的参考来源,摘自here:
internal void DisposeParametersCore(bool excludeInput)
{
if (!this.ParametersDisposed)
{
if (!excludeInput)
{
this.DisposeParameterList(this.InputParameters);
}
this.DisposeParameterList(this.OutputParameters);
IDisposable disposableParameter = this.ReturnParameter as IDisposable;
if (disposableParameter != null)
{
try
{
disposableParameter.Dispose();
}
catch (Exception e)
{
if (Fx.IsFatal(e))
{
throw;
}
this.channelHandler.HandleError(e);
}
}
this.ParametersDisposed = true;
}
}
如您所见,输入参数的处理由布尔参数excludeInput
控制,这提示我该行为是可选的。
我知道,如果我缓存该参数的深层副本,它将避免此问题,但是有什么方法可以关闭特定WCF方法的这种自动行为吗?
这是我要在服务器上缓存的对象类(用C ++ CLI编写):
[Serializable()]
public ref class OpaqueMediaType : ISerializable, IConcreteMediaType {
private:
static const Byte _version = 1;
private:
clr_scoped_ptr<CComPtrIMFMediaType> _ppMediaType;
protected:
virtual DMO_MEDIA_TYPE* __clrcall GetConcreteDMOMediaType() sealed
= IConcreteMediaType::GetConcreteDMOMediaType;
virtual CComPtrIMFMediaType __clrcall GetConcreteMFMediaType() sealed
= IConcreteMediaType::GetConcreteMFMediaType;
virtual void __clrcall FreeConcreteDMOMediaType(DMO_MEDIA_TYPE* pDMOMediaType) sealed
= IConcreteMediaType::FreeConcreteDMOMediaType;
protected:
OpaqueMediaType(SerializationInfo^ info, StreamingContext context);
public:
OpaqueMediaType(DMO_MEDIA_TYPE& dmoMediaType);
OpaqueMediaType(IMFMediaType* pMFMediaType);
OpaqueMediaType(PCM_MediaType pcmMediaType);
virtual void __clrcall GetObjectData(SerializationInfo^ info, StreamingContext context);
PCM_MediaType AsPCM();
};
_ppMediaType
成员正在获得CComPtr派生的类指针的所有权,因此,在处置该实例时,将释放关联的COM对象。由于该成员是可抛弃的,因此IDisposable
类的OpaqueMediaType
接口是由C ++ / CLI自动定义和实现的。
这里是缓存对象的WCF方法:
Task IStorageBackendSvc.AcceptWmaMediaType(int stationId, OpaqueMediaType mediaType) {
try {
WmaWriter wmaWriter = GetWmaWriter(stationId);
wmaWriter.MediaType = mediaType; // parameter object is cached here (shallow copy)
return Task.CompletedTask;
} catch( Exception exception ) {
throw _faultFactory.Wrap(exception);
}
// the `mediaType` parameter is being disposed by WCF at some point AFTER calling this code, releasing the internal COM object held by the cached instance prematurely
}
答案 0 :(得分:1)
使用OperationBehaviorAttribute.AutoDisposeParameters = false
装饰方法的实现(而非合同)即可完成工作。
这是我的合同
[ServiceContract]
public interface IStorageBackendSvc {
...
[OperationContract]
[FaultContract(typeof(DescriptiveFault))]
Task AcceptWmaMediaType(int stationId, OpaqueMediaType mediaType);
}
这是实现:
[OperationBehavior(AutoDisposeParameters = false)]
Task IStorageBackendSvc.AcceptWmaMediaType(int stationId, OpaqueMediaType mediaType) {
try {
WmaWriter wmaWriter = GetWmaWriter(stationId);
wmaWriter.MediaType = mediaType;
return Task.CompletedTask;
} catch( Exception exception ) {
throw _faultFactory.Wrap(exception);
}
}
现在mediaType
参数未放置,因此缓存的引用保持有效,我可以绕过执行该参数的深层复制。
编辑:关于如何缓存对象的疑问,下面是代码:
private Dictionary<int, WmaWriter> _wmaWriters;
private WmaWriter GetWmaWriter(int stationId) {
WmaWriter wmaWriter;
lock( _wmaWriters ) {
if( !_wmaWriters.TryGetValue(stationId, out wmaWriter) ) {
wmaWriter = new WmaWriter(stationId, new DailyFileSplitter(), new WmaFileNameResolver(stationId));
_wmaWriters[stationId] = wmaWriter;
}
}
return wmaWriter;
}
_wmaWriters
是服务成员。它是一本词典,其中包含由电台ID键入的多个WMA作家(我正在录制多个电视台和广播电台)。GetWmaWriter()
方法将返回与特定电台相关联的现有WMA作家,或者它将创建一个新的WMA作家。如果不存在。缓存之所以有效,是因为对于同一工作站ID的多次调用,我将获得相同的WmaWriter实例。