2013-03-12 187 views
2

我有2個ASP.net 3.5 asmx Web服務,ws2ws3。它們分別包含操作op21op31。 op21睡覺2秒和op31睡3秒。我想在異步情況下在網絡服務中調用op11中的op21和op31,同時調用ws1。這樣,當我同步地從客戶端呼叫op11時,所花費的時間將是總共3秒。我目前得到5秒與此代碼:c#.net 3.5中的多線程異步Web服務調用

WS2SoapClient ws2 = new WS2SoapClient(); 
WS3SoapClient ws3 = new WS3SoapClient(); 

//capture time 
DateTime now = DateTime.Now;    
//make calls 

IAsyncResult result1 = ws3.BeginOP31(null,null); 
IAsyncResult result2 = ws2.BeginOP21(null,null); 
WaitHandle[] handles = { result1.AsyncWaitHandle, result2.AsyncWaitHandle }; 

WaitHandle.WaitAll(handles); 

//calculate time difference 
TimeSpan ts = DateTime.Now.Subtract(now); 
return "Asynchronous Execution Time (h:m:s:ms): " + String.Format("{0}:{1}:{2}:{3}", 
ts.Hours, 
ts.Minutes, 
ts.Seconds, 
ts.Milliseconds); 

預期的結果是,兩個請求的總時間應等於花費執行較慢的請求的時間。

請注意,這與我用Visual Studio進行調試時的預期方式一樣,但是在IIS上運行時,時間爲5秒,這似乎表明請求未被同時處理。

我的問題是,是否有IIS和ASMX Web服務的特定配置,可能需要正確設置才能按預期工作?

+0

你在哪裏實施WSxSoapClient?你在用什麼線程?我得到一個STA threa不受支持異常當我嘗試運行您的代碼(用Action.BeginInvoke替換您的Web調用)。 – Aron 2013-03-12 05:12:00

+0

你運行的是什麼版本的.net?使用TPL(.net 4或更高版本)可能是一種更優雅的方式,它也更具可讀性。 – 2013-03-12 05:27:20

+0

@Aron它們是通過添加服務引用自動生成的SOAP客戶端。該服務是使用c#構建的。以下是2個服務/操作,都已部署在本地主機/ IIS上。 公共字符串OP31() { Thread.Sleep(3000); 返回「OP31完成」; } 公共字符串OP21() { Thread.Sleep(2000); 返回「OP21完成」; } – Bowofola 2013-03-12 19:07:36

回答

0

系統中有一些節流。可能該服務僅配置爲一個併發調用程序,這是常見原因(WCF ConcurrencyMode)。服務器上可能存在HTTP級連接限制(ServicePointManager.DefaultConnectionLimit)或WCF限制。

使用Fiddler來確定兩個請求是否被同時發送。使用調試器中斷服務器並查看兩個調用是否同時運行。

+0

它不是wcf。它的ASP.NET web服務 – Bowofola 2013-03-13 10:35:57

+0

好吧,你檢查了其他節流的可能性嗎?你有沒有使用Fiddler等? – usr 2013-03-13 11:06:47

+0

還沒有,我不熟悉小提琴手。但是,當我調試應用程序它按預期工作。我現在已經意識到它是一個iis問題,可能與配置或應用程序池有關,但我不知道它是什麼。 – Bowofola 2013-03-13 11:23:46

1

原來的答案:

我試圖與google.com和bing.com我得到同樣的事情,線性執行。問題在於你正在同一個線程上啓動BeginOP()調用,並且在調用完成之前不會返回AsyncResult(無論出於何種原因)。有點無用。

我的TPL之前的多線程有點生疏,但是我在測試結束時測試了代碼並且它異步執行:這是一個.NET 3.5控制檯應用程序。注意我顯然阻礙了你的一些代碼,但是讓這些類看起來是一樣的。


更新:

我開始第二猜測自己,因爲我的執行時間是如此接近對方,這是令人困惑的。所以我重新編寫了測試,使用Thread.Start()來包含您的原始代碼和我的建議代碼。此外,我在WebRequest方法中添加了Thread.Sleep(N),以便它可以模擬請求的大量不同的執行時間。

測試結果確實顯示您發佈的代碼是按照上面我在原始答案中所述的順序執行的。

Test Results

注的總時間是在兩種情況下不是因爲Thread.sleep代碼的實際web請求時間()長得多。我還添加了Thread.Sleep()來抵消第一個到任何站點的Web請求需要很長時間才能啓動(9秒)的事實,如上所述。無論採取哪種方式,都可以對其進行分割,很明顯,時代在「舊」情況下是連續的,在新情況下是真正的「異步」。


測試了這一點,該更新程序:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Net; 
using System.Text; 
using System.Threading; 

namespace MultiThreadedTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      // Test both ways of executing IAsyncResult web calls 

      ExecuteUsingWaitHandles(); 
      Console.WriteLine(); 

      ExecuteUsingThreadStart(); 
      Console.ReadKey(); 
     } 

     private static void ExecuteUsingWaitHandles() 
     { 
      Console.WriteLine("Starting to execute using wait handles (old way) "); 

      WS2SoapClient ws2 = new WS2SoapClient(); 
      WS3SoapClient ws3 = new WS3SoapClient(); 

      IAsyncResult result1 = null; 
      IAsyncResult result2 = null; 

      // Time the threadas 
      var stopWatchBoth = System.Diagnostics.Stopwatch.StartNew(); 
      result1 = ws3.BeginOP31(); 
      result2 = ws2.BeginOP21(); 

      WaitHandle[] handles = { result1.AsyncWaitHandle, result2.AsyncWaitHandle }; 
      WaitHandle.WaitAll(handles); 

      stopWatchBoth.Stop(); 

      // Display execution time of individual calls 
      Console.WriteLine((result1.AsyncState as StateObject)); 
      Console.WriteLine((result2.AsyncState as StateObject)); 

      // Display time for both calls together 
      Console.WriteLine("Asynchronous Execution Time for both is {0}", stopWatchBoth.Elapsed.TotalSeconds); 
     } 

     private static void ExecuteUsingThreadStart() 
     { 
      Console.WriteLine("Starting to execute using thread start (new way) "); 

      WS2SoapClient ws2 = new WS2SoapClient(); 
      WS3SoapClient ws3 = new WS3SoapClient(); 

      IAsyncResult result1 = null; 
      IAsyncResult result2 = null; 

      // Create threads to execute the methods asynchronously 
      Thread startOp3 = new Thread(() => result1 = ws3.BeginOP31()); 
      Thread startOp2 = new Thread(() => result2 = ws2.BeginOP21()); 

      // Time the threadas 
      var stopWatchBoth = System.Diagnostics.Stopwatch.StartNew(); 

      // Start the threads 
      startOp2.Start(); 
      startOp3.Start(); 

      // Make this thread wait until both of those threads are complete 
      startOp2.Join(); 
      startOp3.Join(); 

      stopWatchBoth.Stop(); 

      // Display execution time of individual calls 
      Console.WriteLine((result1.AsyncState as StateObject)); 
      Console.WriteLine((result2.AsyncState as StateObject)); 

      // Display time for both calls together 
      Console.WriteLine("Asynchronous Execution Time for both is {0}", stopWatchBoth.Elapsed.TotalSeconds); 
     } 
    } 

    // Class representing your WS2 client 
    internal class WS2SoapClient : TestWebRequestAsyncBase 
    { 
     public WS2SoapClient() : base("http://www.msn.com/") { } 

     public IAsyncResult BeginOP21() 
     { 
      Thread.Sleep(TimeSpan.FromSeconds(10D)); 
      return BeginWebRequest(); 
     } 
    } 

    // Class representing your WS3 client 
    internal class WS3SoapClient : TestWebRequestAsyncBase 
    { 
     public WS3SoapClient() : base("http://www.google.com/") { } 

     public IAsyncResult BeginOP31() 
     { 
      // Added sleep here to simulate a much longer request, which should make it obvious if the times are overlapping or sequential 
      Thread.Sleep(TimeSpan.FromSeconds(20D)); 
      return BeginWebRequest(); 
     } 
    } 

    // Base class that makes the web request 
    internal abstract class TestWebRequestAsyncBase 
    { 
     public StateObject AsyncStateObject; 
     protected string UriToCall; 

     public TestWebRequestAsyncBase(string uri) 
     { 
      AsyncStateObject = new StateObject() 
      { 
       UriToCall = uri 
      }; 

      this.UriToCall = uri; 
     } 

     protected IAsyncResult BeginWebRequest() 
     { 
      WebRequest request = 
       WebRequest.Create(this.UriToCall); 

      AsyncCallback callBack = new AsyncCallback(onCompleted); 

      AsyncStateObject.WebRequest = request; 
      AsyncStateObject.Stopwatch = System.Diagnostics.Stopwatch.StartNew(); 

      return request.BeginGetResponse(callBack, AsyncStateObject); 
     } 

     void onCompleted(IAsyncResult result) 
     { 
      this.AsyncStateObject = (StateObject)result.AsyncState; 
      this.AsyncStateObject.Stopwatch.Stop(); 

      var webResponse = this.AsyncStateObject.WebRequest.EndGetResponse(result); 
      Console.WriteLine(webResponse.ContentType, webResponse.ResponseUri); 
     } 
    } 

    // Keep stopwatch on state object for illustration of individual execution time 
    internal class StateObject 
    { 
     public System.Diagnostics.Stopwatch Stopwatch { get; set; } 
     public WebRequest WebRequest { get; set; } 
     public string UriToCall; 

     public override string ToString() 
     { 
      return string.Format("Request to {0} executed in {1} seconds", this.UriToCall, Stopwatch.Elapsed.TotalSeconds); 
     } 
    } 
} 
+0

您在客戶端的BeginOP31方法中調用sleep,這意味着它將以內聯方式執行。這是正常的,因爲異步IO並不僅僅意味着它在另一個線程上調用(不涉及任何線程)。睡眠應該*在服務器上*。我很困惑爲什麼你能夠repro谷歌和MSN肯定沒有延遲大約20秒... – usr 2013-03-13 09:43:35

+0

我打電話睡眠()在任何情況下,它是模擬延遲。它以內聯方式執行,但是使用ThreadStarts,它在單獨的線程上同時執行,因此總執行時間不會超過「較慢」線程的最大休眠時間。我認爲使用小型Web請求是一種很糟糕的測試方法。通常我使用TPL來處理這些事情,比如說如果我在兩臺不同的數據庫服務器上存儲需要運行3分鐘的過程,此代碼(使用我喜歡的任務)將有助於運行兩次,並且只使用3分鐘。 – 2013-03-13 10:01:17

+0

問題在於睡眠方法不在服務器上,而是在肥皂客戶端上。錯誤的解決我的問題。 – Bowofola 2013-03-13 10:42:55