自定义生存期

生存期示例演示如何编写 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 提供程序的方法完成的。

此示例演示如何延迟释放 InstanceContext,空闲超时时间为 20 秒。

扩展 InstanceContext

在 WCF 中,InstanceContext 是连接服务实例和 Dispatcher 的链接。 WCF 允许你通过使用其可扩展对象模式添加新状态或行为来扩展此运行时组件。 在 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 调用 IsIdle 实现中的 IInstanceContextProvider 方法时,此调用将路由到 IsIdleCustomLeaseExtension 方法。 然后,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 方法的调用,并将其路由到 CustomLeaseExtension 方法。 实现 IInstanceContextProvider 包含在类中 CustomLifetimeLease 。 当 WCF 即将释放服务实例时,将调用 IsIdle 方法。 但是,在 ServiceBehavior 的 ISharedSessionInstance 集合中,只有特定 IInstanceContextProvider 实现的一个实例。 这意味着在 WCF 检查 InstanceContext 方法时,无法知道IsIdle是否已关闭。 因此,此示例使用线程锁定来序列化对 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,调度程序将通过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在其空闲时通知调度器至关重要。 如果ICustomLease.IsIdle返回true,则在isIdle中简单地将CustomLifetimeLease专用成员设置为true,并调用回调方法。 由于代码持有锁,因此其他线程无法更改此专用成员的值。 下一次调度程序调用 IInstanceContextProvider.IsIdle时,它会返回 true 并让 Dispatcher 释放实例。

完成自定义扩展的基础后,它必须连接到服务模型。 为了将 CustomLeaseExtension 实现连接到 InstanceContext,WCF 提供了 IInstanceContextInitializer 接口来执行 InstanceContext 的初始化。 在示例中,CustomLeaseInitializer 类实现此接口,并在唯一的初始化方法中向 CustomLeaseExtension 集合添加一个 Extensions 实例。 此方法在初始化 InstanceContext时由 Dispatcher 调用。

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

    //...

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

最后,通过使用 IInstanceContextProvider 实现,将 IServiceBehavior 实现挂钩到服务模型。 此实现放置在类中 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 版本,请按照 生成 Windows Communication Foundation 示例中的说明进行操作。

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