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

c#.net 3.5中的多线程异步web服务调用,c#,.net,web-services,asynchronous,C#,.net,Web Services,Asynchronous,我有两个ASP.net 3.5 asmx web服务,ws2和ws3。它们分别包含操作op21和op31。op21睡眠2秒,op31睡眠3秒。我想从web服务中的op11异步调用op21和op31,ws1。这样,当我从客户端同步调用op11时,所用的时间将是3秒,即总时间。我当前使用此代码获得5秒: WS2SoapClient ws2 = new WS2SoapClient(); WS3SoapClient ws3 = new WS3SoapClient(); //capture time D

我有两个ASP.net 3.5 asmx web服务,ws2ws3。它们分别包含操作op21op31。op21睡眠2秒,op31睡眠3秒。我想从web服务中的op11异步调用op21和op31,ws1。这样,当我从客户端同步调用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);
预期结果是,两个请求的总时间应等于执行较慢请求所需的时间

请注意,当我使用VisualStudio调试它时,它会按预期工作,但是当在IIS上运行它时,时间是5秒,这似乎表明请求不会同时处理


我的问题是,是否需要正确设置IIS和ASMX web服务的特定配置,才能使其按预期工作?

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


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

原始答案:

我在google.com和bing.com上试过,我得到了同样的结果,线性执行。问题在于,您正在同一线程上启动BeginOP()调用,而AsyncResult(无论出于何种原因)在调用完成之前不会返回。有点没用

我的前TPL多线程有点生疏,但我在回答的最后测试了代码,它异步执行:这是一个.NET3.5控制台应用程序。请注意,我显然阻碍了您的一些代码,但使这些类看起来相同


更新:

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);
        }
    }
}
我开始猜测自己,因为我的执行时间如此接近,令人困惑。因此,我重新编写了一点测试,以使用Thread.Start()包含您的原始代码和我建议的代码。此外,我在WebRequest方法中添加了Thread.Sleep(N),这样它就可以模拟请求的不同执行时间

测试结果确实表明,您发布的代码是按顺序执行的,正如我在上面的原始答案中所述

注意,由于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);
        }
    }
}

您在哪里实现WSxSoapClient?你用的是什么线?当我尝试运行您的代码(用Action.BeginInvoke替换您的web调用)时,我收到一个Stathrea不受支持的异常。您运行的是哪个版本的.net?使用TPL(.net 4或更高版本)可能是一种更优雅的方式,可读性也更高。@Aron它们是通过添加服务引用自动生成的SOAP客户端。该服务是使用c#构建的。下面是两个服务/操作,它们都已部署在localhost/IIS上。公共字符串OP31(){Thread.Sleep(3000);返回“OP31 Done”}公共字符串OP21(){Thread.Sleep(2000);返回“OP21 Done”}@DavidHaykin.NET 3.5您是否在本地计算机或服务器上测试此功能?您正在客户端的BeginOP31方法中调用sleep,这意味着它将以内联方式执行。这是正常的,因为异步IO不仅仅意味着在另一个线程上调用它(不涉及任何线程)。睡眠应该在服务器上。;我很困惑,为什么你能用谷歌和msn重新编程,它们肯定没有20秒左右的延迟…我在这两种情况下都调用Sleep(),这是为了模拟延迟。它内联执行,但在threadstart中,它在单独的线程上并发执行,因此总执行时间不会超过“较慢”线程的最大睡眠时间。我认为使用小型web请求是一种糟糕的测试方法。通常我使用TPL来完成这些事情,比如说,如果我在两个不同的DB服务器上运行一个存储过程需要3分钟,那么这段代码(使用我更喜欢的任务)将有助于运行它两次,但仍然只需要3分钟。问题是sleep方法不在服务器上,而是在soap客户端上。我的问题解决方案不正确。但是,我使用线程同步调用实现了这一点,调试时我得到3秒(较慢服务的总时间),从IIS运行时得到5秒(两个服务的总时间)。我需要进行任何配置吗?区别在于,异步只有在从不阻塞时才起作用。只有这样,它才能并发。真正的异步方法就是这样。没有线程正在启动以执行BeginXxx。真正的BeginXxx从来没有延迟。它不是wcf。这是一个ASP.NET web服务好的,您是否检查了其他节流可能性?你用过小提琴等吗?还没有,我对小提琴不熟悉。但是,当我调试应用程序时,它会按预期工作。我现在意识到这是一个iis问题,可能与配置或应用程序池有关,但我还不知道是什么问题。