程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 我的WCF之旅(6):在Winform Application中調用Duplex Service出現TimeoutExce

我的WCF之旅(6):在Winform Application中調用Duplex Service出現TimeoutExce

編輯:關於.NET

我的WCF之旅(6):在Winform Application中調用Duplex Service出現TimeoutException原因和解決

幾個星期之前寫了一篇關於如何通過WCF進行 雙向通信的文章([原創]我的WCF之旅(3):在WCF中實現雙向通信(Bi-directional Communication) ),在文章中我提供了一個如果在Console Application 調用Duplex WCF Service的Sample。前幾天有個網友在上面留言說,在沒有做任何改動得情況下,把 作為Client的ConsoleApplication 換成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#region ICalculatorCallback Members
    public void ShowResult(double x, double y, double result)
    {
      MessageBox.Show(string.Format("x + y = {2} where x = {0} and {1}", x, y, result),"Result", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }

    #endregion
  }
}

接著我們來設計我們的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個截圖)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved