了解状态变化

本主题讨论通道具有的状态和转换、用于构造通道状态的类型以及如何实现它们。

状态机和通道

处理通信的对象(例如套接字)通常呈现状态机,其状态转换与分配网络资源、建立或接受连接、关闭连接和终止通信相关。 通道状态机提供通信对象的状态的统一模型,用于抽象该对象的基础实现。 该 ICommunicationObject 接口提供一组状态、状态转换方法和状态转换事件。 所有通道、通道工厂和通道侦听器都实现通道状态机。

Closed、Closing、Faulted、Opened 和 Opening 事件在发生状态转换后向外部观察者发出信号。

中止、关闭和打开方法(及其异步等效项)会导致状态转换。

state 属性返回由以下项 CommunicationState定义的当前状态:

ICommunicationObject、CommunicationObject 和状态及状态转换

一个 ICommunicationObject 以“已创建”状态开始,可在其中配置其各种属性。 处于打开状态后,对象可用于发送和接收消息,但其属性被视为不可变。 进入“关闭”状态后,对象将无法再处理新的发送或接收请求,但现有请求有机会完成,直到达到“关闭”超时。 如果发生不可恢复的错误,则对象将转换为故障状态,可在其中检查错误的相关信息并最终关闭。 处于“已关闭”状态时,对象基本上已到达状态机的末尾。 对象从一个状态转换为下一个状态后,它不会返回到以前的状态。

下图显示了 ICommunicationObject 状态和状态转换。 状态转换可能是通过调用三种方法之一引起的:中止、打开或关闭。 它们也可能是由调用其他特定于实现的方法引起的。 在打开通信对象时或打开通信对象后,可能会由于错误而转换到出错状态。

每个 ICommunicationObject 都以“已创建”状态开始。 在此状态下,应用程序可以通过设置其属性来配置对象。 对象处于“创建”以外的状态后,该对象被视为不可变。

通道状态转换的数据流图。
图 1. ICommunicationObject 状态机。

Windows Communication Foundation(WCF)提供一个名为CommunicationObject的抽象基类,该基类实现了ICommunicationObject和通道状态机。 下图是特定于 CommunicationObject 的已修改状态关系图。 除了 ICommunicationObject 状态机,它还显示调用附加 CommunicationObject 方法时的计时。

CommunicationObject 实现状态更改的数据流图。 图 2. ICommunicationObject 状态机的 CommunicationObject 实现,包括对事件和受保护方法的调用。

ICommunicationObject 事件

CommunicationObject 公开由 ICommunicationObject.. 定义的五个事件。 这些事件专为使用通信对象的代码设计,可以通知状态转换。 如上图 2 所示,在对象的状态转换为事件命名的状态后,将触发每个事件一次。 所有五个事件都是 EventHandler 类型,如所定义:

public delegate void EventHandler(object sender, EventArgs e);

CommunicationObject 的实现中,发送方要么是 CommunicationObject 本身,要么是传入 CommunicationObject 构造函数的项目(如果使用了该构造函数重载)。 EventArgs 参数e始终为EventArgs.Empty

派生对象回调

除了五个事件之外, CommunicationObject 还声明了八种受保护的虚拟方法,这些方法旨在允许在发生状态转换前后调用派生对象。

每个CommunicationObject.Open方法和CommunicationObject.Close方法都有三个这样的回调与其相关联。 例如,对应于 CommunicationObject.Open 存在 CommunicationObject.OnOpeningCommunicationObject.OnOpen以及 CommunicationObject.OnOpened。 与CommunicationObject.Close相关的是CommunicationObject.OnCloseCommunicationObject.OnClosingCommunicationObject.OnClosed方法。

同样,CommunicationObject.Abort 方法具有相应的 CommunicationObject.OnAbort

虽然CommunicationObject.OnOpenCommunicationObject.OnCloseCommunicationObject.OnAbort没有默认实现,但其他回调确实具有默认实现,这是状态机正确运行所必需的。 如果重写这些方法,请确保调用基实现或正确替换它。

CommunicationObject.OnOpeningCommunicationObject.OnClosingCommunicationObject.OnFaulted触发相应的CommunicationObject.OpeningCommunicationObject.Closing事件和CommunicationObject.Faulted事件。 CommunicationObject.OnOpened 并将 CommunicationObject.OnClosed 对象状态分别设置为“已打开”和“关闭”,然后触发相应的 CommunicationObject.OpenedCommunicationObject.Closed 事件。

状态转换方法

CommunicationObject 提供中止、关闭和打开的实现。 它还提供一种故障方法,该方法会导致状态转换为“故障”状态。 图 2 显示了 ICommunicationObject 状态机,其中每个转换都由导致它的方法标记(未标记的转换发生在导致上次标记转换的方法的实现中)。

注释

通信状态获取/设置的所有 CommunicationObject 实现都是线程同步的。

构造函数

CommunicationObject 提供三个构造函数,所有这些构造函数都使对象保持“已创建”状态。 构造函数定义如下:

第一个构造函数是委托给采用对象的构造函数重载的无参数构造函数:

protected CommunicationObject() : this(new object()) { … }

采用对象的构造函数将该参数用作在同步对通信对象状态的访问时锁定的对象:

protected CommunicationObject(object mutex) { … }

最后,第三个构造函数采用额外的参数,该参数在触发事件时 ICommunicationObject 用作发送方参数。

protected CommunicationObject(object mutex, object eventSender) { … }

前两个构造函数将发送方设置为此对象。

Open 方法

前置条件:状态为“已创建”。

后置条件:状态为“已打开”或“出错”。 可能引发异常。

Open() 方法将尝试打开通信对象并将状态设置为“已打开”。 如果遇到错误,它将状态设置为“出错”。

该方法首先检查当前状态是否为“已创建”。 如果当前状态是“正在打开”或“已打开”,则它将引发 InvalidOperationException。 如果当前状态为“关闭”或“已关闭”,并且对象已终止,则会抛出 CommunicationObjectAbortedException;否则,抛出 ObjectDisposedException。 如果当前状态为“出错”,则会抛出一个 CommunicationObjectFaultedException

然后,它将状态设置为“打开”,并按该顺序调用 OnOpening()(引发打开事件)、OnOpen() 和 OnOpened()。 OnOpened() 将状态设置为 Opened 并引发 Opened 事件。 如果其中任何一项引发异常,Open()将调用 Fault(),并允许异常弹出。 下图更详细地展示了开放过程。

ICommunicationObject.Open 状态更改的数据流关系图。
重写 OnOpen 方法以实现自定义打开逻辑,例如,打开内部通信对象。

Close 方法

前置条件:无。

后置条件:状态为“已关闭”。 可能引发异常。

可以在任何状态下调用 Close() 方法。 它将尝试正常关闭对象。 如果遇到错误,它将终止该对象。 如果当前状态为“关闭”或“已关闭”,则该方法不执行任何操作。 否则,它将状态设置为“关闭”。 如果原始状态为“创建”、“打开”或“出错”,则调用 Abort()(请参阅下图)。 如果原始状态为 Opened,则按该顺序调用 OnClosing()(引发关闭事件)、OnClose() 和 OnClosed()。 如果这其中有任何一项引发异常,Close() 将调用 Abort() 并使异常向上冒泡。 OnClosed() 将状态设置为“已关闭”并引发 Closed 事件。 下图更详细地显示了 Close 过程。

ICommunicationObject.Close 状态更改的数据流关系图。
重写 OnClose 方法以实现自定义关闭机制,例如关闭一个内嵌的通信对象。 可能长时间阻塞的所有正常关闭逻辑(例如,等待他方响应)应在 OnClose() 中实现,原因有两个,一是它采用超时参数,二是它不是作为 Abort() 的一部分调用的。

中止

前置条件:无。
后置条件:状态为“已关闭”。 可能引发异常。

如果当前状态为“已关闭”或对象已被终止(例如,可能是在另一个线程上执行了Abort()方法),则Abort()方法不执行任何操作。 否则,它将状态设置为“关闭”,并按照顺序调用 OnClosing(这会触发“关闭事件”)、OnAbort() 和 OnClosed()。(注意:不调用 OnClose,因为对象正在被终止,而不是简单地关闭。) OnClosed() 将状态设置为“已关闭”并引发 Closed 事件。 如果这其中的任何一项引发异常,它都将被重新引发至 Abort 的调用方。 OnClosing()、OnClosed() 和 OnAbort() 的实现不应阻塞(例如,在输入/输出上)。 下面的关系图更详细地演示了“中止”过程。

ICommunicationObject.Abort 状态更改的数据流关系图。
重写 OnAbort 方法以实现自定义终止逻辑,例如终止内部通信对象。

故障

Fault 方法特定于 CommunicationObject,并不属于 ICommunicationObject 接口的一部分。 为了完整性,此处包含相关内容。

前置条件:无。

后置条件:状态为“出错”。 可能引发异常。

如果当前状态为 Faulted 或 Closed,则 Fault() 方法不执行任何作用。 否则,它将状态设置为 Faulted 并调用 OnFaulted(),这会引发 Faulted 事件。 如果 OnFaulted 引发异常,则将重新引发它。

ThrowIfXxx 方法

CommunicationObject 有三种受保护的方法,可用于在对象处于特定状态时引发异常。

ThrowIfDisposed 如果状态为“正在关闭”、“已关闭”或“已出错”,则引发异常。

ThrowIfDisposedOrImmutable 如果未创建状态,则引发异常。

如果状态不是“已打开”,则 ThrowIfDisposedOrNotOpen 将引发异常。

根据状态引发异常。 下表显示了通过调用引发该状态的 ThrowIfXxx 引发的不同状态和相应的异常类型。

国家 已调用 Abort? 例外
已创建 System.InvalidOperationException
开放 System.InvalidOperationException
已打开 System.InvalidOperationException
关闭 是的 System.ServiceModel.CommunicationObjectAbortedException
关闭 System.ObjectDisposedException
已关闭 是的 如果通过前一个对 Abort 的显式调用来关闭对象,将引发 System.ServiceModel.CommunicationObjectAbortedException。 如果在对象上调用 Close,则会抛出一个 System.ObjectDisposedException
已关闭 System.ObjectDisposedException
已出错 System.ServiceModel.CommunicationObjectFaultedException

超时

在我们讨论的方法中,有一些方法采用超时参数。 它们是 Close、Open(某些重载和异步版本)、OnClose 和 OnOpen。 这些方法旨在允许长时间的操作(例如,在正常断开连接时阻塞输入/输出),以使超时参数指示此类操作在被中断前要耗费多长时间。 上述任一方法的实现应使用提供的超时值来确保它在该超时内返回到调用方。 其他不采用超时的方法的实现不适用于长时间的操作,不应阻塞输入/输出。

未设置超时的 Open() 和 Close() 重载是例外。 这些操作使用派生类提供的默认超时值。 CommunicationObject 公开了两个受保护的抽象属性,分别命名为 DefaultCloseTimeoutDefaultOpenTimeout,定义如下:

protected abstract TimeSpan DefaultCloseTimeout { get; }

protected abstract TimeSpan DefaultOpenTimeout { get; }

派生类实现这些属性,以提供不采用超时值的 Open() 和 Close() 重载的默认超时。 然后,Open() 和 Close() 实现委托给采用超时为它传递默认超时值的重载,例如:

public void Open()

{

this.Open(this.DefaultOpenTimeout);

}

IDefaultCommunicationTimeouts

此接口有四个只读属性,用于提供打开、发送、接收和关闭的默认超时值。 每个实现都负责以适当的方式获取默认值。 为方便起见,ChannelFactoryBaseChannelListenerBase 将这些值默认设置为 1 分钟。