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

转帖地址:http://www.cnblogs.com/artech/archive/2007/03/29/692032.html

 

几个星期之前写了一篇关于如何通过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,很简单,无需多说。

我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案_第1张图片
代码如下

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被锁住,无法响应用户操作。一分后,出现下面的错误。

我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案_第2张图片
我们从上面的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。

我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案_第3张图片
从上图中我们可以很清楚地看出真个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):
我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案_第4张图片
进一步地,由于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成为下面一种方式:
我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案_第5张图片
实现证明这两种方式也是可行的。

4 .疑问

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

你可能感兴趣的:(我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案)