【问题标题】:Multi-threaded async web service call in c# .net 3.5c# .net 3.5 中的多线程异步 Web 服务调用
【发布时间】:2013-03-12 03:55:15
【问题描述】:

我有 2 个 ASP.net 3.5 asmx 网络服务,ws2ws3。它们分别包含操作 op21op31。 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 服务的特定配置才能使其按预期工作?

【问题讨论】:

  • 你在哪里实现 WSxSoapClient?你用的是什么线程?当我尝试运行您的代码(用 Action.BeginInvoke 替换您的网络调用)时,我得到一个 STA threa is not supported 异常。
  • 您运行的是哪个版本的 .net?使用 TPL(.net 4 或更高版本)可能是一种更优雅的方式,也更具可读性。
  • @Aron 它们是通过添加服务引用自动生成的 SOAP 客户端。该服务是使用 c# 构建的。这是 2 个服务/操作,它们都已经部署在 localhost/IIS 上。公共字符串 OP31() { Thread.Sleep(3000);返回“OP31 完成”; } 公共字符串 OP21() { Thread.Sleep(2000);返回“OP21 完成”; }
  • @DavidKhaykin .NET 3.5
  • 你是在本地机器还是服务器上测试这个?

标签: c# .net web-services asynchronous


【解决方案1】:

原答案:

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

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


更新:

我开始怀疑自己,因为我的执行时间非常接近,令人困惑。因此,我稍微重新编写了测试,以包括您的原始代码和我使用 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);
        }
    }
}

【讨论】:

  • 您在客户端的 BeginOP31 方法中调用 sleep,这意味着它将内联执行。这是正常的,因为异步 IO 不仅仅意味着它在另一个线程上被调用(不涉及线程)。睡眠应该在服务器上。;我很困惑为什么您能够使用 google 和 msn 进行复制,而这肯定没有 20 秒左右的延迟......
  • 无论哪种情况,我都在调用 Sleep(),它是为了模拟延迟。它内联执行,但使用 ThreadStarts,它在单独的线程上同时执行,因此总执行时间不超过“较慢”线程的最大睡眠时间。我认为使用小型网络请求是一种糟糕的测试方式。通常我将 TPL 用于此类事情,例如,如果我在 2 个不同的数据库服务器上有一个需要 3 分钟运行的存储过程,则此代码(使用我更喜欢的任务)将有助于运行它两次,但仍然只使用 3 分钟。跨度>
  • 这个问题是sleep方法不在服务器上,而是在soap客户端上。我的问题的错误解决方案。
  • 我已经使用线程同步调用实现了这一点,调试时我得到 3 秒(较慢服务的总时间),从 IIS 运行时我得到 5 秒(两个服务的总时间)。我需要做任何配置吗?
  • 不同之处在于异步只有在它从不阻塞的情况下才有效。只有这样它才能并发。真正的异步方法就是这种情况。没有启动线程来执行 BeginXxx。真正的 BeginXxx 永远不会有延迟
【解决方案2】:

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

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

【讨论】:

  • 它不是 wcf。它是一个 ASP.NET Web 服务
  • 好的,您是否检查了其他节流可能性?你用过Fiddler之类的吗?
  • 还没有,我对提琴手不熟悉。但是当我调试应用程序时,它按预期工作。我现在意识到这是一个 iis 问题,可能与配置或应用程序池有关,但我还不知道是什么。
猜你喜欢
  • 2013-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-19
  • 1970-01-01
  • 1970-01-01
  • 2016-09-14
  • 1970-01-01
相关资源
最近更新 更多