Bowofola
Bowofola

Reputation: 1170

Multi-threaded async web service call in c# .net 3.5

I have 2 ASP.net 3.5 asmx web services, ws2 and ws3. They contain operations op21 and op31 respectively. op21 sleeps for 2 seconds and op31 sleeps for 3 seconds. I want to call both op21 and op31 from op11 in a web service, ws1, asynchronously. Such that when I call op11 from a client synchronously.,the time-taken will be 3 seconds which is the total. I currently get 5 seconds with this code:

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);

The expected result is that the total time for both requests should be equal to the time it takes for the slower request to execute.

Note that this works as expected when I debug it with Visual Studio, however when running this on IIS, the time is 5 seconds which seems to show the requests are not processed concurrently.

My question is, is there a specific configuration with IIS and the ASMX web services that might need to be setup properly for this to work as expected?

Upvotes: 2

Views: 7356

Answers (2)

Dmitriy Khaykin
Dmitriy Khaykin

Reputation: 5258

Original Answer:

I tried this with google.com and bing.com am getting the same thing, linear execution. The problem is that you are starting the BeginOP() calls on the same thread, and the AsyncResult (for whatever reason) is not returned until the call is completed. Kind of useless.

My pre-TPL multi-threading is a bit rusty but I tested the code at the end of this answer and it executes asynchronously: This is a .net 3.5 console app. Note I obviously obstructed some of your code but made the classes look the same.


Update:

I started second-guessing myself because my execution times were so close to each other, it was confusing. So I re-wrote the test a little bit to include both your original code and my suggested code using Thread.Start(). Additionally, I added Thread.Sleep(N) in the WebRequest methods such that it should simulate vastly different execution times for the requests.

The test results do show that the code you posted was sequentially executed as I stated above in my original answer.

Test Results

Note the total time is much longer in both cases than the actual web request time because of the Thread.Sleep(). I also added the Thread.Sleep() to offset the fact that the first web request to any site takes a long time to spin up (9 seconds), as can be seen above. Either way you slice it, it's clear that the times are sequential in the "old" case and truly "asynchronous" in the new case.


The updated program for testing this out:

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);
        }
    }
}

Upvotes: 1

usr
usr

Reputation: 171178

There is some throttling in your system. Probably the service is configured for only one concurrent caller which is a common reason (WCF ConcurrencyMode). There might be HTTP-level connection limits (ServicePointManager.DefaultConnectionLimit) or WCF throttlings on the server.

Use Fiddler to determine if both requests are being sent simultaneously. Use the debugger to break on the server and see if both calls are running simultaneously.

Upvotes: 0

Related Questions