服务契约描述一个通过端点实现的具体操作实现。服务契约参考了消息的格式,以及如何交换的描述。消息的格式进一步以数据契约和消息契约来描述。这一节中包含了服务契约所实现的消息交换模式。
WCF在设计时和运行时都会用到服务契约。在设计时,会在代码中定义那些使用WSDL描述的将来由端点抛出的类。一个标记为[ServiceContract]类,他的方法也会被标记为[OperationContract],将来会使用WSDL抛出,使客户端可以访问到这些操作。运行时,当WCF的调度器接收到以wsdl:operation命名的消息时,即表明源服务中的方法是被[OperationContract]标记的。这些方法的消息就会执行反序列化。
同步请求响应操作
同步请求响应的消息交流模式是最常见的服务操作模式。这种模式对于那些面向过程和面向对象的开发人员来说最熟悉不过。请求响应模式是原始的本地过程调用模式,同时也是最常见的远程过程调用模式。下图中显示了一个运行在客户端的代理发送一个请求到服务端,然后服务同步响应请求的互操作。
WCF使客户端与服务器端的请求响应通信非常简单。设计时,使用Add Service Reference 或者svcutil.exe调用服务的元数据交换(MEX)端点然后生成能够模仿服务器端操作的客户端代理。这样就允许客户端代码调用代理中的方法,就像调用本地方法一样。代理序列化这个方法的名字和参数为一个SOAP消息,并发送消息到服务,同时监听服务端的返回消息,然后根据返回的消息创建一个.NET类型。
下面两个代码段显示了服务和契约的定义。代码中分别定义了一个服务契约和一个操作契约。操作契约描述了一个可以被客户端调用的方法。更准确的说是一个由客户端发送并且由服务端能够理解的消息。注意,契约是定义在街口上的,而不是类。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.ServiceModel;
6:
7: namespace MyFirstWCF
8: {
9: /// <summary>
10: /// 创建一个服务契约
11: ///
12: /// 定义一个实现一些业务功能的.NET类,并且使用WCF属性声明它。[ServiceContract]属性将一个类声明为契约。[ServiceContract]还可以定义一个PortType,来说明表示该服务的WSDL描述。
13: ///
14: /// 7. 定义一个名为“IStockService”的接口,并使用[ServiceContract]属性定义一个命名空间值(http://localhost:8000/WCFStockService)。明确具体的命名空间是一种最好的方式,因为它可以阻止默认命名空间值被加入契约名称中。
15: /// </summary>
16: [ServiceContract(Namespace="http://localhost:8000/WCFStockService")]
17: public interface IStockService
18: {
19:
20: /// <summary>
21: /// 8. 在IStockService中声明一个即将暴露在服务中的方法“GetPrice”,并使用[OperationContract]属性修饰你想暴露在服务中的方法。
22: /// </summary>
23: /// <param name="ticker">需要查询的股票名称</param>
24: /// <returns>返回制定股票当前的价格</returns>
25: [OperationContract]
26: double GetPrice(string ticker);
27: }
28: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.ServiceModel;
6:
7: namespace MyFirstWCF
8: {
9: /// <summary>
10: /// 实现一个服务契约
11: ///
12: /// 这是六项任务中的第二项,在这个步骤中是实现上面定义的契约接口。
13: ///
14: /// 1. 创建一个名为“StockService”的新类,然后使“StockService”类实现前面定义的IStockService接口。
15: /// </summary>
16: public class StockService : IStockService
17: {
18:
19: #region IStockService Members
20:
21: /// <summary>
22: /// 2. 编写StockService类中继承的接口方法实现。
23: /// </summary>
24: double IStockService.GetPrice(string ticker)
25: {
26: return 94.85;
27: }
28:
29: #endregion
30: }
31: }
下面一段代码显示了客户端代码,使用一个调用Add Service Referenct后生成的一个代理。
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.ServiceModel;6:
7: namespace Client8: {
9: class Program10: {
11: #region 使用代码和配置文件共同编写一个客户端12: /// <summary>13: /// 这是六项任务的第四项:14: ///15: /// 这里介绍如何从WCF服务中获得元数据,并创建一个代理类来访问服务。这项任务由WCF的ServiceMode Metadata Utility Tool(Svcutil.exe)完成。16: /// 这个工具可以从服务处获得元数据然后为你当前所选编程语言的代理类生成一个托管源代码文件。除了创建客户端代理外,这个工具还可以为客户端应用程序创建配置文件来连接服务的某个端点。17: ///18: /// 1. 在当前解决方案中创建一个新的应用程序:19: /// a. 在解决方案资源管理器中,右键单击当前包含服务的解决方案,并选择添加,然后选择新建项目。20: /// b. 在添加新项目对话框中,选择VB或C#,然后选择控制台应用程序模板,并将其命名为Client,并保持默认项目路径不变。21: /// c. 点击OK22: ///23: /// 2. 为项目添加System.ServiceModel.dll的引用。24: /// a. 在解决方案资源管理器中的Client项目下右键单击引用文件夹,然后选择添加引用。25: /// b. 选择“最近”标签,然后在列表中选择System.ServiceModel.dll。点击OK。26: /// 因为你已经在我们整个案例的第一步中添加了这个引用,它就可以出现在“最近”选项卡的列表中。当然也可以使用前面的方法,即浏览Windows Communication Foundation目录。27: ///28: /// 3. 在Program.cs中添加引用System.ServiceMOdel命名空间的using语句。29: ///30: /// 4. 启动服务端。31: ///32: /// 5. 运行SvcUtil.exe可以适当的选择创建客户端代码和配置文件:33: /// a. 进入开始菜单中Microsoft Windows SDK下,选择CMD Shell,启动Windows SDK控制台应用程序。34: /// b. 定位到你想要放置客户端代码的目录。如果你已经使用默认路径创建了一个客户端项目,那么目录应该是:35: /// C:\Users\Administrator\Documents\Visual Studio 2008\Projects\MyFirstWCF\Client。36: /// c. 在命令行工具中使用Svcutil.exe来创建客户端代码。如下:37: /// svcutil.exe /language:cs /out:generatedProxy.cs /config:app.config http://localhost:8000/WCFStockService/Service?wsdl38: /// 默认情况下,客户端生成的代理类文件使用的是服务端的文件名(我们的例子中,是StockService.cs或StockService.vb)。39: /// /out参数可以设置客户端代理类文件的名字,/config参数可以设置客户端配置文件名(默认为output.config)。所有这些文件都会被生成在客户端项目的目录下。40: ///41: /// 6. 在Visual Studio中添加生成的代理类文件到客户端项目。在解决方案浏览器中右键点击客户端项目,选择添加现有项。选择上一步骤中生成的generatedProxy文件。42: /// </summary>43: static void Main(string[] args)44: {
45: //这是我们这个例子的最后一步46:
47: //一旦创建并配置完WCF代理,就可以创建客户端实例,使应用程序可以编译并且与WCF服务进行通信。这个步骤中,描述了创建和使用一个WCF客户端。48: //这里我们完成三个操作:创建WCF客户端,使用代理类调用服务的操作。等操作完成后关闭客户端。49:
50: //1. 为你将要调用的服务的基础地址创建一个EndpointAddress实例,然后创建Client对象。51:
52: //2. 调用client中的操作。53:
54: //3. 调用WCF客户端的Close方法,等待用户按下ENTER键结束进程,退出应用程序。55:
56: //Step 1: Create an endpoint address and an instance of the WCF Client.57: StockServiceClient client = new StockServiceClient();58:
59: //Step 2: Call the service operations.60: double price = client.GetPrice("中软国际");61: Console.WriteLine("股票简称:中软国际;单价:{0:c}", price);62:
63: //Step 3: Closing the client gracefully closes the connection and cleans up resources.64: client.Close();
65:
66: Console.WriteLine("Operation is complete.");67: Console.WriteLine("Press <ENTER> to terminate service.");68: Console.WriteLine();
69: Console.ReadLine();
70: }
71: /// <summary>72: /// 使用代码和配置文件编写客户端73: ///74: /// 早在2001年,Visual Studio就引入了添加Web引用,即使用三步操作简化分布式计算中的主要工作。75: /// 这是一件好事,因为它为许多专业的开发者提供了一个可扩展的,给予标准化的分布式计算的切入点。76: /// Visual Studio 2008在继续支持兼容ASMX和更多Web服务的添加Web引用共能同时,引入了添加服务引用(ASR)来支持WCF。77: /// 因为WCF是协议依赖的而且提供序列化的验证、字符编码和安全机制,所以ASR在支持可管理性、性能和安全方面提供极大的灵活性。78: ///79: /// Visual Studio的ASR功能用来从WCF服务获得元数据然后生成代理类和配置文件。其实在幕后,ASR叫做svcutil.exe,是用来调用一
个服务的MEX端点实现请求接口并生成代理类和配置文件的。
80: /// 代理类可以使客户端访问服务中的操作就像访问本地类的方法一样。代理类使用WCF类来根据定义在服务端点上的契约构建和解释SOAP消息。而配置文件存储着服务的ABCs。
81: ///82: ///83: /// </summary>84: #endregion85: }
86: }
下面一段代码显示了从客户端发送到服务端点的SOAP消息。这里有几点值得注意的地方:
- SOAP消息的命名空间是http://tempuri.org/,这个命名空间是默认的,除非在[ServiceContract]属性中重写。如果服务将要暴露在一个应用程序或一个相对较小的组织外,你应该重写默认的。因为命名空间的结构是服务的唯一标示,而且可以在多个服务组合时消除歧义。
- 类定义中的方法名“GetPrice”被用来在SOAP头中构成wsa:Action。而完整的活动的值是一个契约命名空间、契约名、操作名、附加字符串的组合值。
- SOAP体是由方法上的签名和预定义的[OperationContract]和[DataContract]属性控制的。
- SOAP头包括了消息发送的地址。这个例子中是SVC文件在IIS服务器中的地址。
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IStockService/GetPrice</Action> </s:Header> <s:Body> <GetPrice xmlns="http://tempuri.org/"> <ticker>中软国际</ticker> </GetPrice> </s:Body> </s:Envelope>
异步请求响应操作
好的设计会最大限度的减少用户在下一项任务开始前,对当前任务的等待。例如,当一个邮件客户端正在下载新的邮件时,你仍然可以读取或删除已经下载的邮件。或者当一个Web浏览器正在下载网页上嵌入的图片时,你仍然可以滚动页面或者执行跳转。这种一个客户端程序中的多任务形式就是完全的异步设计模式。
在WCF中,请求响应服务操作导致当服务操作运行时,客户端会阻塞。更深一层,通过svcutil.exe生成的客户端代理代码使用阻塞的方式调用负责与服务通信的WCF管道栈。这迫使客户端应用程序在服务调用期间阻塞。如果一个服务需要十秒完成,客户端应用程序将在调用等待返回结果的过程中冻结。
幸运的是,你可以使用.NET Framework中的异步编程模式来构建异步的客户端行为。这种模式可以使任意同步方法的调用者实现异步调用。通过引入IAsyncResult类,创建两个方法,BeginOperationName 和 EndOperationName可以实现。客户端首先调用BeginOperationName方法,然后在当前线程上继续执行代码。与此同时异步操作在另一个线程上执行。对于每一个BegnOperationName的调用,客户端随后会调用EndOperationName方法获得操作的结果。客户端通过一个指向BeginOperationName的委托,这个委托会在异步操作被调用时同时调用,并且可以从BeginOperationName的调用中存储状态信息。
你可以直接添加服务引用来生成异步方法。只要在服务引用设置窗口中点击高级按钮,然后选中生成异步操作多选框。另外,svcutil.exe操作也有/async选项可以用来为每一个服务操作创建Begin<operation>和End<operation>方法。
下图中显示了通过svcutil.exe生成的代理实现异步模式。注意,服务不知道客户端使用异步编程;服务的契约只是定义了请求响应通信,而客户端实现异步模式不需要服务的参与。
下面的示例使用BeginGetPrice和EndGetPrice同时使用IAsyncResult来保存服务操作的状态。BeginGetPrice接受两个参数之外的另一个字符串,用来定义服务操作的输入。第一个参数,AsyncResult。第二个参数可以是任意对象,用来从例行的初始化状态到AsyncCallback状态进行通信。当服务操作完成时,它以AsyncResult.AsyncState属性的形式传递给AsyncCallback。这有助于通过代理初始化服务通信。因此EndGetPrice方法可以在AsyncCallback中获得服务操作的响应结果。静态变量,c,用来防止客户端在服务结束前退出,而Interlocked类用来在多线程环境下确保适当的线程安全。
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.ServiceModel;6: using System.Threading;7:
8: namespace AsyncClient9: {
10: /// <summary>11: /// 首先,添加服务引用,并在服务引用窗口中的点击高级按钮。在
弹出的对话框中选择生成异步操作。
12: /// 其次,定义代理类的名字为“StockServiceClient”。13: /// 第三,定义静态变量c。14: /// </summary>15: class Program16: {
17: /// <summary>18: /// 用来防止客户端在服务结束前退出19: /// </summary>20: static int c = 0;21:
22: static void Main(string[] args)23: {
24: StockServiceClient.StockServiceClient client = new AsyncClient.StockServiceClient.StockServiceClient();25:
26: //保存服务操作的状态27: IAsyncResult arGetPrice;
28:
29: for (int i = 0; i < 10; i++)30: {
31: arGetPrice = client.BeginGetPrice("中软国际",GetPriceCallBack,client);32:
33: Interlocked.Increment(ref c);34: }
35:
36: while (c > 0)37: {
38: Thread.Sleep(1000);
39: Console.WriteLine("Waiting ... Calls outstanding:{0}",c);40: }
41:
42: client.Close();
43: Console.WriteLine("Done!");44:
45: Console.WriteLine("Press <ENTER> to terminate service.");46: Console.WriteLine();
47: Console.ReadLine();
48: }
49: /// <summary>50: /// Asynchronous callbacks for displaying results.51: /// 从例行的初始化状态到AsyncCallback状态进行通信。当服务操作完成时,它以AsyncResult.AsyncState属性的形式传递给AsyncCallback。52: /// 因此EndGetPrice方法可以在AsyncCallback中获得服务操作的响应结果。53: /// </summary>54: /// <param name="ar"></param>55: static void GetPriceCallBack(IAsyncResult ar)56: {
57: double price = ((StockServiceClient.StockServiceClient)ar.AsyncState).EndGetPrice(ar);58: Console.WriteLine("股票简称:中软国际;单价:{0:c}", price);59: Interlocked.Decrement(ref c);60: }
61: }
62: }
单向操作
单向消息交换模式在客户端需要发送消息到服务但是不需要接收响应时使用。使用这一模式,客户端只需要确认成功发送;不需要一个实际的来自服务的响应。有时单向模式被错误的认为是“fire-and-forget”。实际上,他是“fire-and-acknowledge”。因为调用者接受一个确认,以表示消息成功提交到通信管道。
WCF在服务操作级别支持单向消息交换模式。也就是说,服务操作可以被标记为单向模式,然后底层架构会做出相应的优化。当客户端调用一个服务上的单向方法时,或者更准确的说,当客户端发出了一个消息到被标记为单向服务端点的操作,控制器在服务操作完成前就会返回调用者结果。单向操作被定义在[OperationContract]属性中,将IsOneWay=true。下面代码抛出一个包含两个服务操作的服务契约。两者的实现都是相同的,但是其中一个被标记为单向操作。当客户端应用程序调用DoBigAnalysisFast,客户端代理调用马上返回,而不是随服务的Thread.Sleep语句等待十秒。当客户端调用DoBigAnalysisSlow,服务会执行Thread.Sleep语句,客户端代理调用阻塞十秒。
注意,正如其他消息模式,代码不知道被用来传递消息的绑定或者通信协议。只因为netTcpBinding支持双向通信和basicHttpBinding支持请求响应,都可以用来支持单向模式。
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Runtime.Serialization;5: using System.ServiceModel;6: using System.Text;7:
8: namespace WcfOneWayServiceInIIS9: {
10:
11: [ServiceContract]
12: public interface IStockService13: {
14: [OperationContract(IsOneWay=true)]15: void DoBigAnalysisFast(string ticker);16:
17: [OperationContract]
18: void DoBigAnalysisSlow(string ticker);19: }
20: }
1: #region 访问单向服务操作2:
3: static void Main(string[] args)4: {
5: OneWayServiceReference.StockServiceClient client = new OneWayServiceReference.StockServiceClient();6:
7: client.BeginDoBigAnalysisFast("中软国际",DoBigAnalysisFastCallback,client);8: client.BeginDoBigAnalysisSlow("中软国际",DoBigAnalysisSlowCallback,client);9:
10: //client.Close();11:
12: Console.WriteLine("Operation is complete.");13: Console.WriteLine("Press <ENTER> to terminate service.");14: Console.WriteLine();
15: Console.ReadLine();
16: }
17:
18: static void DoBigAnalysisFastCallback(IAsyncResult ar)19: {
20: Console.WriteLine("Do big analysis fast return!");21: }
22:
23: static void DoBigAnalysisSlowCallback(IAsyncResult ar)24: {
25: Console.WriteLine("Do big analysis slow return!");26: }
27:
28: #endregion
双向操作
请求响应的通信是最流行的客户端和服务端消息交换模式。通信由客户端发起,客户端发送一个请求消息到服务,然后服务发送一个响应消息回客户端。如果相应返回的很快,可以使用同步方式,客户端可以阻塞来等待响应。如果在请求与响应之间有一个延迟,一个请求响应模式可以在客户端以异步实现。这种情况下,WCF在发出请求到达服务端后,迅速返回控制到客户端应用程序。相应被客户端接收,一个.NET回调完成。
然而,如果服务需要启动一个消息,如通知或警报?如果客户端和服务需要更高级别的信息,例如许多请求从客户端发出到服务端以获得一个服务响应?如果一个请求预计十分钟才完成?
WCF可以通过双向服务契约。一个双向服务契约实现双向消息模式,这种模式中不请自来的消息在通信频道建立后可以被双向发送。双向频道上的服务可以支持请求相应和单项。
因为消息可以双向通信,即从客户端到服务端或从服务端到客户端,所以双方都需要一个地址、一个绑定和契约来定义向哪里如何发送什么消息。为了促进消息从服务端流回客户端,WCF可能创建一个附加的管道。如果初始化的管道不支持双向通信,WCF会使用与服务端已定义的端点相同协议来创建第二管道,从而使这两个协议对称。如下图所示。
依据从客户端到服务端建立的会话时所使用的绑定,WCF会创建一条或两条管道来实现双向消息模式。针对支持双向通信的协议,例如命名管道和TCP,仅需要一个管道。针对那些不支持双向通信的,如http,WCF会创建一个附加的管道来进行从服务端到客户端的通信。对于预先配置的WCF绑定,附加管道会使用“dual”关键字命名(例如,wsDualHttpBinding)实现两条管道。满足特殊定义的信道的组成元素可以使用自定义绑定。这些自定义绑定也可以通过在管道栈中定义“compositeDuplex”实现双向管道模式。
当从客户端向服务端发送消息时,客户端使用服务端点中定义的地址。相反,当从服务端通过双向管道组合反向发送消息时,服务端必须知道客户端端点地址。客户端宿主的地址,即WCF生成的端点是被WCF管道生成的。服务器端发回客户端的地址可以在设置绑定中的compositDuplex元素的clientBaseAddress属性时被重写。
成对单向契约和双向契约的比较
你可以使用两种不同的消息交换模式来解决双向消息传递的问题。你可以使用一组单向契约或者使用一个独立的双向契约。使用一对单向契约,客户端和服务端是独立的WCF宿主。他们互相暴露端点使对方可以发送消息。因为他们都是正式的服务,所以他们可以暴露多个端点,使用多个绑定,和具有独立版本的契约。使用双向契约,客户端不需要明确的成为WCF服务,不需要具有复杂的绑定或者暴露其他的端点。相反,客户端端点中定义的地址、绑定和契约会在双向通信初始化时由客户端管道工厂实现。下面是对于两者的比较:
成对单向契约
双向契约
契约可以是版本独立的。因为客户端是一个正式的服务,它可以独立暴露并版本化契约的服务。
客户端回调契约由服务端定义。如果服务端版本化它的契约,就需要客户端做出更改。这表明,只有客户端回调的消费者是定义它的服务。
每个单项契约定义它自己的绑定,所以你可以在每个方向上使用不同的协议、字符编码或加密方式。
两个方向上的通信协议讲是相同的,因为他们都是由服务端绑定决定的。
实现双向契约的服务端
一个双向契约包含服务和客户端点的接口定义。这类契约中,服务端契约的一部分是由客户端实现的。
下面代码定义了一个服务契约,这个服务契约提供股票价格的更新。它使用双向通信模式,所以客户端可以注册更新。而且通过调用客户端的PriceUpdate方法,服务端将定期发送更新到客户端。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Runtime.Serialization;
5: using System.ServiceModel;
6: using System.Text;
7:
8: namespace WcfDuplexServiceInIIS
9: {
10: /// <summary>
11: /// 定义一个双向服务契约,一个双向契约包含两个接口。
12: /// 主接口用来从客户端发送消息到服务器端,回调接口用来从服务端回发消息到客户端。
13: /// IClientCallback接口允许执行多种操作,并获得多个结果。
14: /// 这些结果会在每一个操作后回发。
15: ///
16: /// 1. 创建接口,并标示服务端的双向契约。
17: /// 2. 在接口中声明方法RegisterForUpdate。
18: /// 3. 使用OperationContract属性暴露方法。
19: ///
20: /// 7. 在双向契约中关联两个接口,通过设置CallbackContract属性。
21: /// </summary>
22: [ServiceContract(CallbackContract = typeof(IClientCallback))]
23: public interface IStockService
24: {
25: /// <summary>
26: /// 客户端通过调用服务端的RegisterForUpdates操作初始化通信。
27: /// </summary>
28: [OperationContract(IsOneWay=true)]
29: void RegisterForUpdates(string ticker);
30: }
31: }
1: using System;2: using System.Web;3: using System.Web.Services;4: using System.Web.Services.Protocols;5: using System.ComponentModel;6: using System.ServiceModel;7:
8: namespace WcfDuplexServiceInIIS9: {
10: /// <summary>11: /// 回调接口用来从服务端回发消息到客户端。12: /// PriceUpdate方法将会在操作完成后返回现在的结果。13: ///14: /// 4. 创建回调接口,定义服务端可以在客户端调用的操作。15: /// 5. 定义回调接口中的操作。16: /// 6. 使用OperationContract属性暴露接口中的操作。17: /// </summary>18: public interface IClientCallback19: {
20: [OperationContract(IsOneWay=true)]21: void PriceUpdate(string ticker, double price);22: }
23: }
1: using System;2: using System.Web;3: using System.Web.Services;4: using System.Web.Services.Protocols;5: using System.ComponentModel;6: using System.Threading;7:
8: namespace WcfDuplexServiceInIIS9: {
10: public class Update11: {
12: public IClientCallback callback;13:
14: public void SendUpdateToClient()15: {
16: Random p = new Random();17: for (int i = 0; i < 10; i++)18: {
19: Thread.Sleep(5000); //更新在此发生20: try21: {
22: callback.PriceUpdate("中软国际", 100 + p.NextDouble());23: }
24: catch (Exception ex)25: {
26: Console.WriteLine("Error sending cache to client: {0}", ex.Message);27: }
28: }
29: }
30: }
31: }
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Runtime.Serialization;5: using System.ServiceModel;6: using System.Text;7: using System.Threading;8:
9: namespace WcfDuplexServiceInIIS10: {
11: /// <summary>12: /// 实现双向服务契约的服务端类使用PerSession的InstanceContextMode存储结果。13: /// 一个服务的实例将被绑定到每个双向会话上。14: /// </summary>15: public class StockService : IStockService16: {
17: #region IStockService Members18: /// <summary>19: /// 这种创建方式不是一个好的规则。20: /// 每个客户端一个线程。应该颠倒为每个股票创建一个线程。21: /// </summary>22: /// <param name="ticker"></param>23: void IStockService.RegisterForUpdates(string ticker)24: {
25: Update bgWorker = new Update();26: bgWorker.callback = OperationContext.Current.GetCallbackChannel<IClientCallback>();
27:
28: Thread t = new Thread(new ThreadStart(bgWorker.SendUpdateToClient));29: t.IsBackground = true;30: t.Start();
31: }
32:
33: #endregion34: }