在 WCF 服务调用中禁用输入参数的自动处理

Disable automatic disposal of Input Parameters in WCF service call

我有一个为单例实例配置的 WCF 服务器。我的客户端作为参数传递给服务方法,一个实现 IDisposable 接口的对象,服务器试图缓存这个实例,但是,WCF 运行时在某个时候自动处理该参数 服务方法执行后,过早地销毁了我的缓存实例。

通过查看堆栈跟踪,我发现在 MessageRpc.DisposeParametersCore() 方法内部调用了参数的 Dispose() 调用。

这是该方法的参考来源,取自 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;
            }
        }

如您所见,输入参数的处理由 bool 参数 excludeInput 控制,这暗示我此行为是可选的。

我知道如果我缓存参数的 深拷贝 它将避免这个问题,但是有什么方法可以关闭特定 WCF 方法的这种自动行为?

这是我试图在服务器上缓存的对象 class(用 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 派生的 class 指针的所有权,因此当实例被释放时,关联的 COM 对象将被释放。由于此成员是一次性的,因此 OpaqueMediaType class 的 IDisposable 接口由 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
      }

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 是现役军人。它是一个字典,包含多个 WMA 编写器,由一个电台 ID 键入(我正在录制多个电视和广播电台) GetWmaWriter() 方法将 return 与特定电台相关联的现有 WMA 编写器,或者它将如果 none 存在,则创建一个新的。缓存是有效的,因为对于同一个站点 ID 的多次调用,我将得到相同的 WmaWriter 实例。