第十二讲:服务寄宿

代码

https://yunpan.cn/cPns5DkGnRGNs   密码:3913

不是特殊的Demo,我们不再贴实例Demo的图片了,直接去网盘找相应的项目看


 

在之前的几节课当中,我们用过控制台应用程序 寄宿,WinFrom 程序寄宿  以及 IIS 寄宿  做了一些简单的应用,但是我们都没有深入的去讲解他们之间的不同。 那么这节课 我们 就围绕这个话题 ,在它的理论和原理上更深入的讲解。

 

任何一个程序都需要运行一个确定的进程中,进程是一个容器,其中包含程序实例运行所需要的资源。同理,一个WCF服务的监听与执行同样需要一个进程来承载。我们将WCF服务创建或指定的一个进程的方式称为服务寄宿(Service Hosting).服务寄宿的本质是通过某种方式创建或指定一个进程,用以监听服务的请求和服务执行服务操作,为服务提供一个运行环境.

服务寄宿的方式大体分两种:

1).一种是为一组WCF服务创建一个托管的应用程序,通过手工启动程序的方式对服务进行寄宿。所有托管的应用程序均可作为WCF服务的寄宿,比如控制台应用,Windows Forms应用程序和ASP.NET应用程序。我们把这种服务寄宿方式称为自我寄宿(Self Hosting);

2).另一种则是通过操作系统现有的进程激活方式为WCF服务提供宿主,Windows下的进程激活手段包括IIS,Windows Service等.

 

服务寄宿的手段是为一个WCF服务类型创建一个ServiceHost 对象。无论采用哪种寄宿方式,在为某个服务创建ServiceHost的过程中,WCF框架内部会执行一系列的操作,其中最重要的步骤就是为服务创建服务描述。

WCF服务描述通过类型System.ServiceModel.Description.ServiceDescription表示,ServiceDescription对象是WCF服务运行时的描述(最终被用来为服务生成WSDL文档和支持元数据交换)。除了包含WCF服务的一些基本信息,比如服务的名称,命名空间和CLR类型等。ServiceDescription还包括服务所有终结点和服务行为的描述。

 

创建ServiceHost的最主要任务就是根据服务类型创建ServiceDescription对象,这个被创建的ServiceDescription对象最终被赋值给ServiceHost的Description属性。

 

我们现在就来介绍一下这个ServiceDescription是如何一步一步被创建的:

1:将传入的ServiceType作为ServiceDescription的ServiceType属性。通过反射得到应用在服务类型的ServiceBehaviorAttribute特性,如果该特性存在并且为服务设置了Name,Namespace等属性,则用它们覆盖ServiceDescription同名属性的默认值.

2:通过反射得到通过自定义特性方式和配置方式应用到服务类型上的服务行为。这些服务行为通过反射得到的ServiceBehaviorAttribute特性一起组成了ServiceDescription的服务行为集合;

3:通过解析服务的配置,得到为服务配置的所有终结点和相应的终结点行为,并创建相应的ServiceEndpoint对象。这些创建的ServiceEndpoint对象最终被添加到ServiceDescription的Endpoints集合属性中。对于自我寄宿,在成功创建了ServiceHost后,可以通过AddServiceEndPoint方法添加额外的终结点,这些终结点也会被添加到Endpoints集合中.

4:对于每个终结点的Contract,通过反射应用在服务契约类型上的ServiceContractAttribute特性创建相应的ContractDescription。然后通过反射获得以自定义特性方式应用在服务契约上的契约行为,并将它们纳入契约的Behaviors集合属性;

5:对于ContractDescription中表示操作的每个OperationDescription,通过反射应用在操作方法上的OperationContractAttribute特性创建OperationDescription,然后通过反射的机制获得以自定义特性的方式应用在方法上的操作行为.

 

 

关闭ServiceHost:(这里我们先了解下,后面我们会更深的探讨)

一旦你初始化并打开ServiceHost实例,就为每一个端点创建了信道侦听器以处理每一个端点的请求。如果宿主进程已关闭,每一个ServiceHost实例也被关闭无论是通过显式地调用Close()或Dispose()方法,还是当类型已分配时隐士地被服务模型关闭。当ServiceHost被关闭后,到达特定ServiceHost实例的新请求将被拒绝,但是已经在等待队列中的请求会被处理.

 

 

通信事件:

ServiceHost继承自ServiceHostBase,而ServiceHostBase又继承自CommunicationObject.CommunicationObject是很多参与通信信道的对象的通用基础类型,它提供了一致的状态机制。这个类型公开的事件包括Opening,Opened,Closing,Closed和Faulted.为了给每一个寄存的宿主通知信道栈中的状态变化,你可以通过这些通信事件来完成.在写事件日志,或者当服务的通信信道被关闭或遇到问题需要通知管理员时。Closing和Faulted可能会十分有用。

例如:

在你打开通信信道之前,可在ServiceHost实例中添加如下的事件处理程序.

1 using(ServiceHost host=new ServiceHost(typeof(HelloIndigo.HelloIndigoService)))
2 {
3    host.Closed+=new EventHandler(host_Closed);
4    host.Closing+=new EventHandler(host_Closing);
5    host.Faulted+=new EventHandler(host_Faulted);
6    host.Open();
7 }

 

 


 

 

前面我们都在回顾我们之前的例子知识 的总结

 

现在我们开始真正说  本节的正题了

 

WCF服务的自我寄宿(Self-Hosting):

 

Windows寄宿:

Windows Forms和WPF应用均有助于寄存带有UI的WCF服务。在这种情况下,UI线程保持应用处于存活状态。因此,任何ServiceHost实例在应用生命周期内都可以接受请求,除非它被显式地关闭了.

Windwos应用通过消息响应提供功能。每一个Windows应用都有一个消息队列和一个包含消息循环的Window过程。消息循环从消息队列中检索消息,并一个接一个地处理它们--------通常是将消息转发到相应的目标Window中。  

Windows应用一般包含运行于UI线程上的用户界面,表明消息是从消息循环来处理的。当WCF服务寄存在一个Windows应用中时,操作可以在线程池线程或UI线程上执行--------这由服务及其寄宿在设计时的选择来确定。

 

当服务被寄宿在Windows应用中,并被寄宿在UI线程中时,UI线程就像消息处理的一个节流阀。就是说,消息按照顺序,一次处理一个请求。为了达到更高的吞吐量(同时处理多个请求),需要避免被寄存在UI线程中。

当你在UI线程中创建ServiceHost时,所有的请求都通过消息循环被处理。在一个支持多线程的服务中,这样会限制多条消息被同时处理,因为消息循环每次只能处理一条消息。这样做的好处之一就是,请求可以自由的与控件属性进行交互,因为它们都在同一

线程中。

宿主和服务都有一些手段去控制服务是否在UI线程中运行。宿主可以控制线程构建ServiceHost.服务可以拒绝UI线程使用ServiceBehaviorAttribute的UseSynchronizationContext属性.

Windows应用中寄宿服务的默认行为就是在UI线程中处理请求.

 

例如这样的寄宿 就是  在UI线程中处理请求(Windows应用中寄宿服务的默认行为):

1 Public partial class Form1:Form
2 {
3     ServiceHost m_serviceHost;
4     public Form1()
5      {
6            InitializeComponent();
7            m_serviceHost=new ServiceHost(typeof(Messaging.MessagingService));
8      }
9 }

当请求是通过消息循环被处理时,服务操作可以自由地与诸如用户界面,设置控件属性等进行交互。唯一的缺点就是,消息循环就像请求处理的一只节流阀一样,即使将服务配置成允许处理多个并发请求,也很难完成。

 

通俗的讲就是  UI线程  有个  专门的  消息的执行队列 ,而WCF 也将寄宿在  这个UI线程中,所以   这个消息执行队列中 不光有 WCF 需要执行的 操作,也包括 UI上面的操作(例如 点击按钮触发事件等等),那么伴随的一个问题出来了,如果UI上的某个按钮需要执行很长时间,那么后面的 执行事件等待,当然也包括WCF的执行事件,那如果UI线程 锁死,例如 无响应,那后面的 消息执行列队也一样停滞不会执行。也就是说  WCF 的执行事件 被约束在 UI线程的执行队列当中 是一件不太好的事情。当然  也会根据具体的业务 设计来设计这个WCF 寄宿方式。

 

 

 

如何解决呢?

从同一个Windows Form应用出发,如果你在UI线程启动前构建ServiceHost实现,该宿主实例将运行在自己的线程中。这就意味着消息是由分配自线程池的工作线程来处理.而不是传递给消息循环(也就是说,在Ui线程启动前,启动ServiceHost的Open方法,那么ServiceHost就是由线程池专门分配的线程,与Ui线程就不再一起了,也不会将执行事件放到UI下执行事件队列中去)。这使得服务可以处理多个并发请求。但是,当与控件和其他共享应用资源通信时,却需要提供多线程保护机制.

 

 

当需要足够吞吐量的服务来说,在UI线程中处理请求并不现实。很多服务都要求服务器的不同级别的吞吐量,而且没有关联任何用户界面。然而,使用一些服务操作却一定会与UI线程交互,而另一些则无需与UI线程交互,但是它们都要求吞吐量。服务可以要求运行在一个单独的线程内以确保用ServiceBehaviorAttribute来表征的吞吐量。

 

正如我们曾经提到过的,你可以控制在哪一个线程上构建ServiceHost,无论是在宿主或服务实现中。如果你在UI线程中创建ServiceHost,消息就由该线程处理,如果在UI线程被创建之前创建ServiceHost,ServiceHost在初始化时会默认地创建一个新线程。也就是说,在一般情况下,服务参与UI的同步上下文,而在后一种情况下,服务创建一个新的同步上下文。同步上下文指向代码其中执行的线程.下面的代码显示了如何在Application.Run()之前构建ServiceHost.这意味着服务将在一个新的同步上下文中被执行.

 

 

如果当前同步上下文不是UI线程,就会为ServiceHost创建一个新的同步上下文。正如前面所说的,这就意味着消息是由来自线程池的线程来处理,而不是通过消息循环来处理。为了与UI线程同步,宿主会在UI线程被创建之后创建每一个ServiceHost实例。这一点在服务操作与UI交互时十分有利

 

例如:

 1     static class Program
 2     {
 3         public static ServiceHost MessageServiceHost;
 4 
 5         /// <summary>
 6         /// 应用程序的主入口点。
 7         /// </summary>
 8         [STAThread]
 9         static void Main()
10         {
11 
12             //这里在UI线程启动之前寄宿(这样就创建一个新的线程,而不会与UI同一个线程)
13             Program.MessageServiceHost = new ServiceHost(typeof(Messaging.MessagingService));
14             //打开状态
15             Program.MessageServiceHost.Open();
16 
17             Application.EnableVisualStyles();
18             Application.SetCompatibleTextRenderingDefault(false);
19             //执行到这里就开始创建UI线程
20             Application.Run(new Form1());
21         }
22     }

 

 

 

ServiceHost生命周期:

在默认情况下,ServiceHost实例的生命周期就是其线程的生命周期。同时,这也意味着windows应用程序的生命周期也是一样的。除非显示地调用ServiceHost的Close()方法。如果你允许关闭应用程序,却没有首先关闭ServiceHost及其相关的信道监听器,则在应用准备关闭信道时消息可能还会继续流进。为了避免这种宿主应用关闭和客户请求到达时间段里的紊乱情况,最好是能够显示的关闭每一个ServiceHost实例.

 

当宿主运行时,你也可以调用ServiceHost的Open()和Close()方法,在UI线程上运行时,用来启动和中止服务。当ServiceHost不在UI线程上运行时,Close()将结束初始化它的线程。这就清除了ServiceHost实例并放弃任何不再有用的引用。在这种情况下,你的应用必须重新创建和打开ServiceHost以接受随后的请求.

 

例如:

 1         private void Form1_FormClosing()
 2         {
 3             DialogResult result = MessageBox.Show("确定要关闭吗");
 4             if (result == DialogResult.Yes)
 5             {
 6                 if (m_serviceHost != null)
 7                 {
 8                     m_serviceHost.Close();
 9                     m_serviceHost = null;
10                 }
11             }
12             else
13             {
14                 e.Cancel = true;
15             }
16         }

 

为了更便于实现,ServiceHost应该在应用正在退出以防止再处理新的请求时被关闭。在创建ServiceHost的文件Program.cs中,加入部分代码构建ApplicationExit事件.并调用ServiceHost的Close()方法以加快这个过程.

 

例如这样,在全局应用程序 中 去执行,在程序退出的时候 执行的事件:

 1 Static void Main()
 2 {
3 Application.ApplicationExit+=new EventHandler(Application_ApplicationExit); 4 } 5
6 Static void Application_ApplicationExit(object sender,EventArgs e) 7 { 8 if(Program.MessageServiceHost!=null) 9 { 10 Program.MessageServiceHost.Close(); 11 Program.MessageServiceHost=null; 12 } 13 }

 


 

 

上面我们说 在 Winfrom  窗体 被创建(也就是说UI线程被创建)之前 调用ServiceHost 的 Open 方法,这是一种解决办法

 

那么我们还可以这么做

 

服务也可以利用ServiceBehaviorAttribute的UseSynchronizationContext属性来阻止与UI线程同步。该属性的默认值为true,这就是为什么只要存在一个UI线程,服务就会参与其中的原因。如果将该属性值设置为false,即使宿主在UI线程上构建了ServiceHost,也会使用新的线程。你可以使用如下的方式将该属性应用到服务类型上.

 

 1     /// <summary>
 2     /// 服务契约
 3     /// </summary>
 4     [ServiceBehavior(UseSynchronizationContext = false)]// 阻止与UI线程同步
 5     public class CalculatorService : ICalculator
 6     {
 7         public double Add(double x, double y)
 8         {
 9             throw new NotImplementedException();
10         }
11     }

 

 

 

下面我们看一个Demo ( 云盘本节课的 Windows Form寄宿 ) ,这个Demo  使用 上面第二中方法 。

 

先看这个项目的结构:

[ 12-01 ]

 

Client 客户端层

Contracts服务契约层

Hosting 服务器寄宿层(这里使用了一个 WinFrom 的寄宿方式)

Services服务契约实现层

 

仔细观察代码,再 服务契约实现层上加特性  [ServiceBehavior(UseSynchronizationContext = false)]  与 不加该特性的区别

 

运行项目,首先运行 Hosting,再运行 Client。

 

不加  [ServiceBehavior(UseSynchronizationContext = false)]  这个特性时:

[ 12-02 ]

 

再看我们加上[ServiceBehavior(UseSynchronizationContext = false)] 这个特性时:

[ 12-03 ]

 

仔细观察 两次启动的执行时间以及线程ID。

第一次不加特性明显 感觉 消息被阻塞处理,使用的都是同一个线程。也就是说 线程等于 9 的这个线程 ,就是我们的UI线程。

而第二次加了特性的  时间都一样,而且都不是一个线程执行的操作。

 

很明显的第二次的速度要快很多,因为使用了多线程。

 

posted @ 2016-05-18 18:23  徐朗  阅读(646)  评论(0编辑  收藏  举报