自定义生存期

生存期示例演示了如何编写一个 Windows Communication Foundation (WCF) 扩展,为共享的 WCF 服务实例提供自定义生存期服务。

注意

本文的最后介绍了此示例的设置过程和生成说明。

共享实例化

WCF 为服务实例提供了多种实例化模式。 本文章中介绍的共享实例化模式提供了一种在多个通道之间共享一个服务实例的方法。 客户端可以联系服务中的工厂方法,并创建一个新通道来启动通信。 以下代码片段演示了客户端应用程序如何创建一个到现有服务实例的新通道:

// Create a header for the shared instance id
MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
        CustomHeader.HeaderName,
        CustomHeader.HeaderNamespace,
        Guid.NewGuid().ToString());

// Create the channel factory
ChannelFactory<IEchoService> channelFactory =
    new ChannelFactory<IEchoService>("echoservice");

// Create the first channel
IEchoService proxy = channelFactory.CreateChannel();

// Call an operation to create shared service instance
using (new OperationContextScope((IClientChannel)proxy))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy.Echo("Apple"));
}

((IChannel)proxy).Close();

// Create the second channel
IEchoService proxy2 = channelFactory.CreateChannel();

// Call an operation using the same header that will reuse the shared service instance
using (new OperationContextScope((IClientChannel)proxy2))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy2.Echo("Apple"));
}

与其他实例化模式不同,共享实例化模式拥有一种释放服务实例的独特方式。 默认情况下,为 InstanceContext 关闭所有通道时,WCF 服务运行时会检查服务 InstanceContextMode 是否配置为 PerCallPerSession;如果是,则释放实例并声明资源。 如果正在使用自定义 IInstanceContextProvider,WCF 会先调用提供程序实现的 IsIdle 方法,然后才释放实例。 如果 IsIdle 返回 true,则释放实例;否则,IInstanceContextProvider 实现负责用回调方法来通知 Dispatcher 处于空闲状态。 这是通过调用提供程序的 NotifyIdle 方法来实现的。

此示例演示了可如何将空闲超时设置为 20 秒来延迟释放 InstanceContext

扩展 InstanceContext

在 WCF 中,InstanceContext 是服务实例和 Dispatcher 之间的链接。 借助 WPF,可使用其可扩展对象模式添加新的状态或行为来扩展此运行时组件。 在 WCF 中的可扩展对象模式下,可使用新功能来扩展现有的运行时类,或者向对象添加新的状态功能。 可扩展对象模式中有三个接口:IExtensibleObject<T>IExtension<T>IExtensionCollection<T>

IExtensibleObject<T> 接口由对象实现,后者允许使用可自定义其功能的扩展。

IExtension<T> 接口由可作为类型为 T 的类扩展的对象来实现。

最后,IExtensionCollection<T> 接口是 IExtension<T> 实例的集合,它允许按其类型来检索 IExtension<T> 的实现。

因此,为了扩展 InstanceContext,必须实现 IExtension<T> 接口。 在此示例项目中,CustomLeaseExtension 类包含此实现。

class CustomLeaseExtension : IExtension<InstanceContext>
{
}

IExtension<T> 接口具有 AttachDetach 两个方法。 顾名思义,当运行时将扩展附加到 InstanceContext 类的实例以及将扩展从该类的实例分离时,将会调用这两个方法。 在此示例中,Attach 方法用于跟踪属于当前扩展实例的 InstanceContext 对象。

InstanceContext owner;

public void Attach(InstanceContext owner)
{
    this.owner = owner;
}

此外,还必须将必要的实现添加到该扩展以提供扩展的生存期支持。 因此,将使用所需的方法声明 ICustomLease 接口并在 CustomLeaseExtension 类中实现该接口。

interface ICustomLease
{
    bool IsIdle { get; }
    InstanceContextIdleCallback Callback { get; set; }
}

class CustomLeaseExtension : IExtension<InstanceContext>, ICustomLease
{
}

当 WCF 调用 IInstanceContextProvider 实现中的 IsIdle 方法时,此调用将路由到 CustomLeaseExtensionIsIdle 方法。 然后,CustomLeaseExtension 将检查其专用状态,以查看 InstanceContext 是否处于空闲状态。 如果它处于空闲状态,则返回 true。 否则,它将针对指定的扩展生存期启动一个计时器。

public bool IsIdle
{
  get
  {
    lock (thisLock)
    {
      if (isIdle)
      {
        return true;
      }
      else
      {
        StartTimer();
        return false;
      }
    }
  }
}

在该计时器的 Elapsed 事件中,将调用 Dispatcher 中的回调函数以启动另一个清理周期。

void idleTimer_Elapsed(object sender, ElapsedEventArgs args)
{
    lock (thisLock)
    {
        StopTimer();
        isIdle = true;
        Utility.WriteMessageToConsole(
            ResourceHelper.GetString("MsgLeaseExpired"));
        callback(owner);
    }
}

获得实例移动到空闲状态的新消息时,将无法更新正在运行的计时器。

此示例实现 IInstanceContextProvider 以截获对 IsIdle 方法的调用,并将这些调用路由到 CustomLeaseExtensionIInstanceContextProvider 实现包含在 CustomLifetimeLease 类中。 WCF 要释放服务实例时,将调用 IsIdle 方法。 但是,在 ServiceBehavior 的 IInstanceContextProvider 集合中,只有特定 ISharedSessionInstance 实现的一个实例。 这意味着在 WCF 检查 IsIdle 方法时,将无法识别 InstanceContext 是否已关闭。 因此,此示例使用线程锁定将请求序列化到 IsIdle 方法。

重要

由于序列化可能会严重影响应用程序的性能,因此不推荐使用线程锁定方法。

专用成员字段在 CustomLifetimeLease 类中用于跟踪空闲状态,由 IsIdle 方法返回。 每次调用 IsIdle 方法时,都将返回 isIdle 字段,并将其重置为 false。 必须将此值设置为 false,以确保调度程序调用 NotifyIdle 方法。

public bool IsIdle(InstanceContext instanceContext)
{
    get
    {
        lock (thisLock)
        {
            //...
            bool idleCopy = isIdle;
            isIdle = false;
            return idleCopy;
        }
    }
}

如果 IInstanceContextProvider.IsIdle 方法返回 false,则 Dispatcher 会使用 NotifyIdle 方法注册一个回调函数。 此方法将接收对所释放的 InstanceContext 的引用。 因此,该示例代码可查询 ICustomLease 类型扩展,并检查处于扩展状态的 ICustomLease.IsIdle 属性。

public void NotifyIdle(InstanceContextIdleCallback callback,
            InstanceContext instanceContext)
{
    lock (thisLock)
    {
       ICustomLease customLease =
           instanceContext.Extensions.Find<ICustomLease>();
       customLease.Callback = callback;
       isIdle = customLease.IsIdle;
       if (isIdle)
       {
             callback(instanceContext);
       }
    }
}

在检查 ICustomLease.IsIdle 属性之前,需要设置 Callback 属性;否则,CustomLeaseExtension 变为空闲状态后就无法通知 Dispatcher。 如果 ICustomLease.IsIdle 返回 true,则 isIdle 私有成员仅在 CustomLifetimeLease 中设置为 true,并调用该回调方法。 由于该代码持有一个锁,因此其他线程无法更改此专用成员的值。 Dispatcher 下次调用 IInstanceContextProvider.IsIdle 时,会返回 true 并让 Dispatcher 释放实例。

由于自定义扩展的基础工作已完成,因此必须将其挂钩到服务模型。 为了将 CustomLeaseExtension 实现挂钩到 InstanceContext,WCF 提供了 IInstanceContextInitializer 接口来启动 InstanceContext。 在此示例中,CustomLeaseInitializer 类实现此接口,并将 CustomLeaseExtension 的一个实例从仅方法初始化添加到 Extensions 集合。 此方法在初始化 InstanceContext 时由调度程序调用。

public void InitializeInstanceContext(InstanceContext instanceContext,
    System.ServiceModel.Channels.Message message, IContextChannel channel)

    //...

    IExtension<InstanceContext> customLeaseExtension =
        new CustomLeaseExtension(timeout, headerId);
    instanceContext.Extensions.Add(customLeaseExtension);
}

最后,通过使用 IServiceBehavior 实现,将 IInstanceContextProvider 实现挂钩到服务模型。 此实现将放置在 CustomLeaseTimeAttribute 类中,它还派生自 Attribute 基类,以将此行为作为特性公开。

public void ApplyDispatchBehavior(ServiceDescription description,
           ServiceHostBase serviceHostBase)
{
    CustomLifetimeLease customLease = new CustomLifetimeLease(timeout);

    foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;

        if (cd != null)
        {
            foreach (EndpointDispatcher ed in cd.Endpoints)
            {
                ed.DispatchRuntime.InstanceContextProvider = customLease;
            }
        }
    }
}

可使用 CustomLeaseTime 特性对此行为进行批注,以将其添加到示例服务类中。

[CustomLeaseTime(Timeout = 20000)]
public class EchoService : IEchoService
{
  //…
}

运行示例时,操作请求和响应将显示在服务和客户端控制台窗口中。 在每个控制台窗口中按 Enter 可以关闭服务和客户端。

设置、生成和运行示例

  1. 请确保已执行 Windows Communication Foundation 示例的一次性安装过程

  2. 若要生成 C# 或 Visual Basic .NET 版本的解决方案,请按照 Building the Windows Communication Foundation Samples中的说明进行操作。

  3. 要使用单机配置或跨计算机配置来运行示例,请按照运行 Windows Communication Foundation 示例中的说明进行操作。