我的WCF之旅(6):在Winform Application中調(diào)用Duplex Service出現(xiàn)TimeoutException的原因和解決方案
1.問(wèn)題重現(xiàn)
首先我們來(lái)重現(xiàn)這個(gè)錯(cuò)誤,在這里我只寫(xiě)WinForm的代碼,其他的內(nèi)容請(qǐng)參考我的文章。Client端的Proxy Class(DuplexCalculatorClient)的定義沒(méi)有任何變化。我們先來(lái)定義用于執(zhí)行回調(diào)操作(Callback)的類(lèi)——CalculatorCallbackHandler.cs。代碼很簡(jiǎn)單,就是通過(guò)Message Box的方式顯示運(yùn)算的結(jié)果。
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
}
}
接著我們來(lái)設(shè)計(jì)我們的UI,很簡(jiǎn)單,無(wú)需多說(shuō)。

代碼如下
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);
}
}
}
}
啟動(dòng)Host,然后隨啟動(dòng)Client,在兩個(gè)Textbox中輸入數(shù)字2和3,Click Calculate按鈕,隨后整個(gè)UI被鎖住,無(wú)法響應(yīng)用戶操作。一分后,出現(xiàn)下面的錯(cuò)誤。
我們從上面的Screen Shot中可以看到這樣一個(gè)很有意思的現(xiàn)象,運(yùn)算結(jié)果被成功的顯示,顯示,但是有個(gè)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.原因分析
在我開(kāi)始分析為什么會(huì)造成上面的情況之前,我要申明一點(diǎn):由于找不到任何相關(guān)的資料,以下的結(jié)論是我從試驗(yàn)推導(dǎo)出來(lái),我不能保證我的分析是合理的,因?yàn)橛行┘?xì)節(jié)我自己都還不能自圓其說(shuō),我將在后面提到。我希望有誰(shuí)對(duì)此了解的人能夠指出我的問(wèn)題, 我將不勝感激。
我們先來(lái)看看整個(gè)調(diào)用過(guò)程的Message Exchange過(guò)程,通過(guò)前面相關(guān)的介紹,我們知道WCF可以采用三種不同的Message Exchange Pattern(MEP)——One-way,Request/Response,Duplex。其實(shí)從本質(zhì)上講,One-way,Request/Response是兩種基本的MEP, Duplex可以看成是這兩種MEP的組合——兩個(gè)One-way,兩個(gè)Request/Response或者是一個(gè)One-way和一個(gè)Request/Response。在定義Service Contract的時(shí)候,如果我們沒(méi)有為某個(gè)Operation顯式指定為One-way (IsOneWay = true), 那么默認(rèn)采用Request/Response方式。我們現(xiàn)在的Sample就是由兩個(gè)Request/Response MEP組成的Duplex MEP。

從上圖中我們可以很清楚地看出真?zhèn)€Message Exchange過(guò)程,Client調(diào)用Duplex Calculator Service,Message先從Client傳遞到Service,Service執(zhí)行Add操作,得到運(yùn)算結(jié)果之后,從當(dāng)前的OperationContext獲得Callback對(duì)象,發(fā)送一個(gè)Callback 請(qǐng)求道Client(通過(guò)在Client注冊(cè)的Callback Channel:http://localhost:6666/myClient)。但是,由于Client端調(diào)用Calculator Service是在主線程中,我們知道一個(gè)UI的程序的主線程一直處于等待的狀態(tài),它是不會(huì)有機(jī)會(huì)接收來(lái)自Service端的Callback請(qǐng)求的。但是由于Callback Operation是采用Request/Response方式調(diào)用的,所以它必須要收到來(lái)自Client端Reply來(lái)確定操作正常結(jié)束。這實(shí)際上形成了一個(gè)Deadlock,可以想象它用過(guò)也不能獲得這個(gè)Reply,所以在一個(gè)設(shè)定的時(shí)間內(nèi)(默認(rèn)為1分鐘),它會(huì)拋出Timeout 的Exception, Error Message就像下面這個(gè)樣子。
”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:多線程異步調(diào)用
既然WinForm的主線程不能接受Service的Callback,那么我們就在另一個(gè)線程調(diào)用Calculator Service,在這個(gè)新的線程接受來(lái)自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);
}
}
通過(guò)實(shí)驗(yàn)證明,這種方式是可行的。
方案2:采用One-way的方式調(diào)用Service 和Callback,既然是因?yàn)镋xception發(fā)生在不同在規(guī)定的時(shí)間內(nèi)不能正常地收到對(duì)應(yīng)的Reply,那種我就 允許你不必收到Reply就好了——實(shí)際上在本例中,對(duì)于Add方法,我們根本就不需要有返回結(jié)果,我們完全可以使用One-way的方式調(diào)用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);
}
}

進(jìn)一步地,由于Callback也沒(méi)有返回值,我們也可以把Callback操作也標(biāo)記為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);
}
}
那么現(xiàn)在的Message Exchange成為下面一種方式:

實(shí)現(xiàn)證明這兩種方式也是可行的。
4 .疑問(wèn)
雖然直到現(xiàn)在,所有的現(xiàn)象都說(shuō)得過(guò)去,但是仍然有一個(gè)問(wèn)題不能得到解釋?zhuān)喝绻且驗(yàn)閃inform的主線程不能正常地接受來(lái)自Service的Callback才導(dǎo)致了Timeout Exception,那為什么Callback操作能過(guò)正常執(zhí)行呢?而且通過(guò)我的實(shí)驗(yàn)證明他基本上是在拋出Exception的同時(shí)執(zhí)行的。(參考第2個(gè)截圖)
WCF相關(guān)內(nèi)容:
[原創(chuàng)]我的WCF之旅(1):創(chuàng)建一個(gè)簡(jiǎn)單的WCF程序
[原創(chuàng)]我的WCF之旅(2):Endpoint Overview
[原創(chuàng)]我的WCF之旅(3):在WCF中實(shí)現(xiàn)雙向通信(Bi-directional Communication)
[原創(chuàng)]我的WCF之旅(4):WCF中的序列化(Serialization)- Part I
[原創(chuàng)]我的WCF之旅(4):WCF中的序列化(Serialization)- Part II
[原創(chuàng)]我的WCF之旅(5):Service Contract中的重載(Overloading)
[原創(chuàng)]我的WCF之旅(6):在Winform Application中調(diào)用Duplex Service出現(xiàn)TimeoutException的原因和解決方案
[原創(chuàng)]我的WCF之旅(7):面向服務(wù)架構(gòu)(SOA)和面向?qū)ο缶幊蹋∣OP)的結(jié)合——如何實(shí)現(xiàn)Service Contract的繼承
[原創(chuàng)]我的WCF之旅(8):WCF中的Session和Instancing Management
[原創(chuàng)]我的WCF之旅(9):如何在WCF中使用tcpTrace來(lái)進(jìn)行Soap Trace
[原創(chuàng)]我的WCF之旅(10): 如何在WCF進(jìn)行Exception Handling
[原創(chuàng)]我的WCF之旅(11):再談WCF的雙向通訊-基于Http的雙向通訊 V.S. 基于TCP的雙向通訊
[原創(chuàng)]我的WCF之旅(12):使用MSMQ進(jìn)行Reliable Messaging
[原創(chuàng)]我的WCF之旅(13):創(chuàng)建基于MSMQ的Responsive Service


幾個(gè)星期之前寫(xiě)了一篇關(guān)于如何通過(guò)WCF進(jìn)行 雙向通信的文章,在文章中我提供了一個(gè)如果在Console Application 調(diào)用Duplex WCF Service的Sample。前幾天有個(gè)網(wǎng)友在上面留言說(shuō),在沒(méi)有做任何改動(dòng)得情況下,把 作為Client的Console Application 換成Winform Application,運(yùn)行程序的時(shí)候總是出現(xiàn)Timeout的錯(cuò)誤。我覺(jué)得這是一個(gè)很好的問(wèn)題,通過(guò)這個(gè)問(wèn)題,我們可以更加深入地理解WCF的消息交換的機(jī)制。


浙公網(wǎng)安備 33010602011771號(hào)