几个星期之前写了一篇关于如何通过WCF进行 双向通信的文章([原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication) ),在文章中我提供了一个如果在Console Application 调用Duplex WCF Service的Sample。前几天有个网友在上面留言说,在没有做任何改动得情况下,把 作为Client的Console Application 换成Winform Application,运行程序的时候总是出现Timeout的错误。我觉得这是一个很好的问题,通过这个问题,我们可以更加深入地理解WCF的消息交换的机制。

1.问题重现

首先我们来重现这个错误,在这里我只写WinForm的代码,其他的内容请参考我的文章。Client端的Proxy Class(DuplexCalculatorClient)的定义没有任何变化。我们先来定义用于执行回调操作(Callback)的类——CalculatorCallbackHandler.cs。代码很简单,就是通过Message Box的方式显示运算的结果。

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Artech.DuplexWCFService.Contract;
using System.ServiceModel;

namespace Artech. WCFService.Client
{
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class CalculatorCallbackHandler : ICalculatorCallback
    {
        ICalculatorCallback Members
    }
}

接着我们来设计我们的UI,很简单,无需多说。


代码如下

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Artech. WCFService.Client
{
    public partial class Form1 : Form
    {
        private DuplexCalculatorClient _calculator;
        private double _op1;
        private double _op2;
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            this._calculator = new DuplexCalculatorClient(new System.ServiceModel.InstanceContext(new CalculatorCallbackHandler()));
        }
        private void Calculate()
        {
            this._calculator.Add(this._op1, this._op2);
        }
        private void buttonCalculate_Click(object sender, EventArgs e)
        {
            if (!double.TryParse(this.textBoxOp1.Text.Trim(), out this._op1))
            {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }
            if (!double.TryParse(this.textBoxOp2.Text.Trim(), out this._op2))
            {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }
            try
            {
                this.Calculate();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            } 
        }
    }
}

启动Host,然后随启动Client,在两个Textbox中输入数字2和3,Click Calculate按钮,随后整个UI被锁住,无法响应用户操作。一分后,出现下面的错误。


我们从上面的Screen Shot中可以看到这样一个很有意思的现象,运算结果被成功的显示,显示,但是有个Exception被抛出:”This request operation sent to http://localhost:6666/myClient/4f4ebfeb-5c84-45dc-92eb-689d631b337f did not receive a reply within the configured timeout (00:00:57.7300000). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.”。

2.原因分析

在我开始分析为什么会造成上面的情况之前,我要申明一点:由于找不到任何相关的资料,以下的结论是我从试验推导出来,我不能保证我的分析是合理的,因为有些细节我自己都还不能自圆其说,我将在后面提到。我希望有谁对此了解的人能够指出我的问题, 我将不胜感激。

我们先来看看整个调用过程的Message Exchange过程,通过前面相关的介绍,我们知道WCF可以采用三种不同的Message Exchange Pattern(MEP)——One-way,Request/Response,Duplex。其实从本质上讲,One-way,Request/Response是两种基本的MEP, Duplex可以看成是这两种MEP的组合——两个One-way,两个Request/Response或者是一个One-way和一个Request/Response。在定义Service Contract的时候,如果我们没有为某个Operation显式指定为One-way (IsOneWay = true), 那么默认采用Request/Response方式。我们现在的Sample就是由两个Request/Response MEP组成的Duplex MEP。


从上图中我们可以很清楚地看出真个Message Exchange过程,Client调用Duplex Calculator Service,Message先从Client传递到Service,Service执行Add操作,得到运算结果之后,从当前的OperationContext获得Callback对象,发送一个Callback 请求道Client(通过在Client注册的Callback Channel:http://localhost:6666/myClient)。但是,由于Client端调用Calculator Service是在主线程中,我们知道一个UI的程序的主线程一直处于等待的状态,它是不会有机会接收来自Service端的Callback请求的。但是由于Callback Operation是采用Request/Response方式调用的,所以它必须要收到来自Client端Reply来确定操作正常结束。这实际上形成了一个Deadlock,可以想象它用过也不能获得这个Reply,所以在一个设定的时间内(默认为1分钟),它会抛出Timeout 的Exception, Error Message就像下面这个样子。

”This request operation sent to http://localhost:6666/myClient/4f4ebfeb-5c84-45dc-92eb-689d631b337f did not receive a reply within the configured timeout (00:00:57.7300000). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.”。

3.解决方案

方案1:多线程异步调用

既然WinForm的主线程不能接受Service的Callback,那么我们就在另一个线程调用Calculator Service,在这个新的线程接受来自Service的Callback。

于是我们改变Client的代码:

private void buttonCalculate_Click(object sender, EventArgs e)
        {
            if (!double.TryParse(this.textBoxOp1.Text.Trim(), out this._op1))
            {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }

            if (!double.TryParse(this.textBoxOp2.Text.Trim(), out this._op2))
            {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }
            try
            {
                Thread newThread = new Thread(new ThreadStart(this.Calculate));
                newThread.Start();        
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }             
        }

通过实验证明,这种方式是可行的。

方案2:采用One-way的方式调用Service 和Callback,既然是因为Exception发生在不同在规定的时间内不能正常地收到对应的Reply,那种我就 允许你不必收到Reply就好了——实际上在本例中,对于Add方法,我们根本就不需要有返回结果,我们完全可以使用One-way的方式调用Operation。在这种情况下,我们只需要改变DuplexCalculator和CalculatorCallback的Service Contract定义就可以了。

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.DuplexWCFService.Contract
{
    [ServiceContract(CallbackContract = typeof(ICalculatorCallback))]
    public interface IDuplexCalculator
    {
        [OperationContract(IsOneWay =true)]
        void Add(double x, double y);
    }
}

从Message Exchange的角度讲,这种方式实际上是采用下面一种消息交换模式(MEP):

进一步地,由于Callback也没有返回值,我们也可以把Callback操作也标记为One-way.

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.DuplexWCFService.Contract
{
    //[ServiceContract]
    public interface ICalculatorCallback
    {
        [OperationContract(IsOneWay = true)]
        void  ShowResult(double x, double y, double result);
    }
}

那么现在的Message Exchange成为下面一种方式:

实现证明这两种方式也是可行的。

4 .疑问

虽然直到现在,所有的现象都说得过去,但是仍然有一个问题不能得到解释:如果是因为Winform的主线程不能正常地接受来自Service的Callback才导致了Timeout Exception,那为什么Callback操作能过正常执行呢?而且通过我的实验证明他基本上是在抛出Exception的同时执行的。(参考第2个截图)

WCF相关内容:
[原创]我的WCF之旅(1):创建一个简单的WCF程序
[原创]我的WCF之旅(2):Endpoint Overview
[原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication)
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part I
[原创]我的WCF之旅(4):WCF中的序列化(Serialization)- Part II
[原创]我的WCF之旅(5):Service Contract中的重载(Overloading)
[原创]我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
[原创]我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承
[原创]我的WCF之旅(8):WCF中的Session和Instancing Management
[原创]我的WCF之旅(9):如何在WCF中使用tcpTrace来进行Soap Trace
[原创]我的WCF之旅(10): 如何在WCF进行Exception Handling
[原创]我的WCF之旅(11):再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯
[原创]我的WCF之旅(12):使用MSMQ进行Reliable Messaging
[原创]我的WCF之旅(13):创建基于MSMQ的Responsive Service

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文链接

我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案...相关推荐

  1. 我的WCF之旅(13):创建基于MSMQ的Responsive Service

    一.One-way MEP V.S. Responsible Service 我们知道MSMQ天生就具有异步的特性,它只能以One-way的MEP(Message Exchange Pattern)进 ...

  2. 在C# winform程序中调用WPF写的数学公式编辑器

    由于工作原因,需要在程序中加入数学公式编辑功能,因此在网上找了不少开源数学公式程序.经过比较,最终选择了Math-Editor-master程序(可以在github上搜索此名称). 我的程序(简称主程 ...

  3. 我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承...

    当今的IT领域,SOA已经成为了一个非常时髦的词,对SOA风靡的程度已经让很多人对SOA,对面向服务产生误解.其中很大一部分人甚至认为面向服务将是面向对象的终结,现在的面向对象将会被面向服务完全代替. ...

  4. 我的WCF之旅 (11): 再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯...

    在一个基于面向服务的分布式环境中,借助一个标准的.平台无关的Communication Infrastructure,各个Service通过SOAP Message实现相互之间的交互.这个交互的过程实 ...

  5. 《我的WCF之旅》博文系列汇总

    WCF是构建和运行互联系统的一系列技术的总称,它是建立在Web Service架构上的一个全新的通信平台.你可以把它看成是.NET平台上的新一代的Web Service.WCF为我们提供了安全.可靠的 ...

  6. 我的WCF之旅(10):如何在WCF进行Exception Handling

    在任何Application的开发中,对不可预知的异常进行troubleshooting时,异常处理显得尤为重要.对于一般的.NET系统来说,我们简单地借助try/catch可以很容易地实现这一功能. ...

  7. 我的WCF之旅(1):创建一个简单的WCF程序

    http://www.cnblogs.com/artech/archive/2007/02/26/656901.html 为了使读者对基于WCF的编程模型有一个直观的映像,我将带领读者一步一步地创建一 ...

  8. [转载]我的WCF之旅(3):在WCF中实现双工通信

    http://www.cnblogs.com/artech/archive/2007/03/02/661969.html 双工(Duplex)模式的消息交换方式体现在消息交换过程中,参与的双方均可以向 ...

  9. C#发现之旅 --- WinForm.NET中开发具有固定背景图片的可滚动控件

    摘要 在本文章中笔者使用WinForm.NET2.0开发出一个具有固定背景图片的带滚动条的容器控件.点击下载本文章配套的演示程序 /Files/xdesigner/FixedBackground.zi ...

最新文章

  1. SpringBoot简单使用
  2. php目录间拷贝文件方法
  3. JavaScript学习笔记(3)
  4. git只合并某一个分支的某个commit
  5. [转载] Linux信号基础
  6. 两个sql交集_如何使用性能分析工具定位SQL执行慢的原因?
  7. 转:Xcode下的GDB调试命令
  8. python可视化工具好用_6款Python必备的可视化工具推荐
  9. ubuntu下面 将桌面换成 英文
  10. Weak Pair HDU - 5877 树状数组+离散化+DFS遍历
  11. java 二维数组 floyd_Floyd算法(一)之 C语言详解
  12. 知乎招聘搜索算法实习生!邀你共建知乎搜索引擎!
  13. php显示图片缩略图,使用ThinkPHP生成缩略图及显示的方法
  14. 【js】【cornerstone】cornerstone使用url方式加载图像
  15. 大数据对人们的好处_大数据有什么作用和优势
  16. 官方文档---ubuntu 安装OpenStack
  17. 怎样增加Dave 英语学习小组
  18. JQuery中操作Css样式
  19. json react 展示工具_如何基于jsoneditor二次封装一个可实时预览的json编辑器组件?(react版)...
  20. Flutter混编工程之高速公路Pigeon

热门文章

  1. 构建商品评价的分类器
  2. 导出勾选密码永不过期的AD账户信息
  3. BBSSDK插件技术方案
  4. 团队项目第一次冲刺 第二天
  5. 2017年大数据的十大发展趋势
  6. 《C和指针》一1.7 问题
  7. 挑灯熬夜看《Build 2015 Keynote》图文笔记
  8. [RGEOS]空间拓扑关系
  9. Linux下ibus-sunpinyin的安装及翻页快捷键设置!
  10. 关于端口聚合或端口聚合称呼的误区