2013-03-12 186 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