Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to call many web services?

I have 30 sub companies and every one has implemented their web service (with different technologies).

I need to implement a web service to aggregate them, for example, all the sub company web services have a web method with name GetUserPoint(int nationalCode) and I need to implement my web service that will call all of them and collect all of the responses (for example sum of points).

This is my base class:

public abstract class BaseClass
{ // all same attributes and methods
  public long GetPoint(int nationalCode);
}

For each of sub companies web services, I implement a class that inherits this base class and define its own GetPoint method.

public class Company1
{
  //implement own GetPoint method (call a web service).
}

to

public class CompanyN
{
  //implement own GetPoint method (call a web service).
}

so, this is my web method:

        [WebMethod]
        public long MyCollector(string nationalCode)
        {

           BaseClass[] Clients = new BaseClass[]  { new Company1(),//... ,new Company1()}

           long Result = 0;
           foreach (var item in Clients)
           {
                long ResultTemp = item.GetPoint(nationalCode);
                Result += ResultTemp;
           }
       return Result;
       }

OK, it works but it's so slow, because every sub companys web service is hosted on different servers (on the internet).

I can use parallel programing like this:(is this called parallel programing!?)

    foreach (var item in Clients)
    {
                    Tasks.Add(Task.Run(() =>
                        {
                        Result.AddRange(item.GetPoint(MasterLogId, mobileNumber));                  
                    }
      }

I think parallel programing (and threading) isn't good for this solution, because my solution is IO bound (not CPU intensive)!

Call every external web service is so slow, am i right? Many thread that are pending to get response!

I think async programming is the best way but I am new to async programming and parallel programing.

What is the best way? (parallel.foreach - async TAP - async APM - async EAP -threading)

Please write for me an example.

like image 420
Sonador Avatar asked Jan 06 '16 13:01

Sonador


People also ask

How to call a web services?

The primary method of calling a Web Service is sending a SOAP request message via HTTP and receiving a SOAP response message (or fault). However, ASP.NET Web Services also allow two additional means of calling Web Services: HTTP-GET and HTTP-POST.


1 Answers

It's refreshing to see someone who has done their homework.

First things first, as of .NET 4 (and this is still very much the case today) TAP is the preferred technology for async workflow in .NET. Tasks are easily composable, and for you to parallelise your web service calls is a breeze if they provide true Task<T>-returning APIs. For now you have "faked" it with Task.Run, and for the time being this may very well suffice for your purposes. Sure, your thread pool threads will spend a lot of time blocking, but if the server load isn't very high you could very well get away with it even if it's not the ideal thing to do.

You just need to fix a potential race condition in your code (more on that towards the end).

If you want to follow the best practices though, you go with true TAP. If your APIs provide Task-returning methods out of the box, that's easy. If not, it's not game over as APM and EAP can easily be converted to TAP. MSDN reference: https://msdn.microsoft.com/en-us/library/hh873178(v=vs.110).aspx

I'll also include some conversion examples here.

APM (taken from another SO question):

MessageQueue does not provide a ReceiveAsync method, but we can get it to play ball via Task.Factory.FromAsync:

public static Task<Message> ReceiveAsync(this MessageQueue messageQueue)
{
    return Task.Factory.FromAsync(messageQueue.BeginReceive(), messageQueue.EndPeek);
}

...

Message message = await messageQueue.ReceiveAsync().ConfigureAwait(false);

If your web service proxies have BeginXXX/EndXXX methods, this is the way to go.

EAP

Assume you have an old web service proxy derived from SoapHttpClientProtocol, with only event-based async methods. You can convert them to TAP as follows:

public Task<long> GetPointAsyncTask(this PointWebService webService, int nationalCode)
{
    TaskCompletionSource<long> tcs = new TaskCompletionSource<long>();

    webService.GetPointAsyncCompleted += (s, e) =>
    {
        if (e.Cancelled)
        {
            tcs.SetCanceled();
        }
        else if (e.Error != null)
        {
            tcs.SetException(e.Error);
        }
        else
        {
            tcs.SetResult(e.Result);
        }
    };

    webService.GetPointAsync(nationalCode);

    return tcs.Task;
}

...

using (PointWebService service = new PointWebService())
{
    long point = await service.GetPointAsyncTask(123).ConfigureAwait(false);
}

Avoiding races when aggregating results

With regards to aggregating parallel results, your TAP loop code is almost right, but you need to avoid mutating shared state inside your Task bodies as they will likely execute in parallel. Shared state being Result in your case - which is some kind of collection. If this collection is not thread-safe (i.e. if it's a simple List<long>), then you have a race condition and you may get exceptions and/or dropped results on Add (I'm assuming AddRange in your code was a typo, but if not - the above still applies).

A simple async-friendly rewrite that fixes your race would look like this:

List<Task<long>> tasks = new List<Task<long>>();

foreach (BaseClass item in Clients) {
    tasks.Add(item.GetPointAsync(MasterLogId, mobileNumber));                  
}

long[] results = await Task.WhenAll(tasks).ConfigureAwait(false);

If you decide to be lazy and stick with the Task.Run solution for now, the corrected version will look like this:

List<Task<long>> tasks = new List<Task<long>>();

foreach (BaseClass item in Clients)
{
    Task<long> dodgyThreadPoolTask = Task.Run(
        () => item.GetPoint(MasterLogId, mobileNumber)
    );

    tasks.Add(dodgyThreadPoolTask);                  
}

long[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
like image 69
Kirill Shlenskiy Avatar answered Oct 04 '22 03:10

Kirill Shlenskiy