c#.net 3.5中的多线程异步Web服务调用

时间:2013-03-12 03:55:16

标签: c# .net web-services asynchronous

我有2个ASP.net 3.5 asmx网络服务, ws2 ws3 。它们分别包含操作 op21 op31 。 op21睡眠 2秒,op31睡眠 3秒。我想在Web服务 ws1 中异步调用 op11 中的op21和op31。这样,当我同步从客户端调用 op11 时,所用的时间将是 3秒,即总数。我目前使用以下代码获得 5秒

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服务的特定配置才能使其按预期工作?

2 个答案:

答案 0 :(得分: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);
        }
    }
}

答案 1 :(得分:0)

您的系统中存在一些限制。可能只为一个并发调用者配置服务,这是一个常见原因(WCF ConcurrencyMode)。服务器上可能存在HTTP级别的连接限制(ServicePointManager.DefaultConnectionLimit)或WCF限制。

使用Fiddler确定是否同时发送这两个请求。使用调试器在服务器上中断并查看两个调用是否同时运行。