WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)
对于一般的多线程操作,比如异步地进行基于文件系统的IO操作;异步地调用Web Service;或者是异步地进行数据库访问等等,是和具体的线程无关的。也就是说,对于这些操作,任意创建一个新的线程来执行都是等效的。但是有些情况下,有些操作却只能在固定的线程下执行。比如,在GUI应用下,对控件的访问就需要在创建该控件的线程下执行;或者我们在某个固定的线程中通过TLS(Thread Local Storage)设置了一些Context信息,供具体的操作使用,我们把操作和某个固定的线程的依赖称为线程关联性(Thread Affinity)。在这种情况下,我们的异步操作就需要被Marshal到固定的线程执行。在WCF并发或者Callback的情况下也具有这样的基于线程关联性的问题。
一、从基于Windows Application客户端的WCF回调失败谈起
在"我的WCF之旅"系列文章中,有一篇(html" target=_blank>WinForm Application中调用Duplex Service出现TimeoutException的原因和解决方案)专门介绍在一个Windows Application客户端应用, 通过WCF 的Duplex通信方式进行回调失败的文章.我们今天以此作为出发点介绍WCF在Thread Affinity下的表现和解决方案.
我们来创建一个WCF的应用来模拟该场景: 客户端是一个基于Windows Form应用, 完成一个计算器的功能, 用户输入操作数,点击"计算"按钮, 后台通过调用WCF service, 并传递一个用于显示计算结果的Callback对象; service进行相应的计算得到最后的运算结果,调用该Callback对象将运算结果显示到客户端界面.这是我们的WCF四层结构:
1、Contract:ICalculate & ICalculateCallback
1: namespace Artech.ThreadAffinity.Contracts
2: {
3: [ServiceContract(CallbackContract = typeof(ICalculateCallback))]
4: public inte易做图ce ICalculate
5: {
6: [OperationContract]
7: void Add(double op1, double op2);
8: }
9: }这是Service Contract,下面是Callback Contract,用于显示运算结果:
1: namespace Artech.ThreadAffinity.Contracts
2: {
3: public inte易做图ce ICalculateCallback
4: {
5: [OperationContract]
6: void DisplayResult(double result);
7: }
8: }2、Service:CalculateService
1: namespace Artech.ThreadAffinity.Services
2: {
3: [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
4: public class CalculateService:ICalculate
5: {
6: public static ListBox DisplayPanel
7: { get; set; }
8:
9: #region ICalculate Members
10:
11: public void Add(double op1, double op2)
12: {
13: double result = op1 + op2;
14: ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();
15:
16: DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result));
17:
18: callback.DisplayResult(result);
19: }
20:
21: #endregion
22: }
23: }
由于需要进行callback, 我们把ConcurrencyMode 设为Reentrant。当得到运算的结果后,通过OperationContext.Current.GetCallbackChannel得到callback对象,并调用之。还有一点需要提的是,该service是通过一个Windows Form application进行host的。并且有一个ListBox列出所有service执行的结果,就像这样:
3、Hosting
Hosting的代码写在Form的Load事件中:
1: private void HostForm_Load(object sender, EventArgs e)
2: {
3: this._serviceHost = new ServiceHost(typeof(CalculateService));
4: CalculateService.DisplayPanel = this.listBoxResult;
5: CalculateService.SynchronizationContext = SynchronizationContext.Current;
6: this._serviceHost.Opened += delegate
7: {
8: this.Text = "The calculate service has been started up!";
9: };
10:
11: this._serviceHost.Open();
12: }我们注意到了CalculateService使用到的用于显示所有预算结果的ListBox就是在这了通过static property传递的。
这么配置文件
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service name="Artech.ThreadAffinity.Services.CalculateService">
5: <endpoint binding="netTcpBinding" bindingConfiguration="" contract="Artech.ThreadAffinity.Contracts.ICalculate" />
6: <host>
7: <baseAddresses>
8: <add baseAddress="net.tcp://
补充:综合编程 , 其他综合 ,