Best way to call many web services?

后端 未结 2 874
予麋鹿
予麋鹿 2021-02-07 22:08

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,

相关标签:
2条回答
  • 2021-02-07 22:28

    You can create an async version of the GetPoint:

    public abstract class BaseClass
    { // all same attributes and methods
        public abstract long GetPoint(int nationalCode);
    
        public async Task<long> GetPointAsync(int nationalCode)
        {
             return await GetPoint(nationalCode);
        }
    }
    

    Then, collect the tasks for each client call. After that, execute all tasks using Task.WhenAll. This will execute them all in parallell. Also, as pointed out by Kirill, you can await the results of each task:

    var tasks = Clients.Select(x => x.GetPointAsync(nationalCode));
    long[] results = await Task.WhenAll(tasks);
    

    If you do not want to make the aggregating method async, you can collect the results by calling .Result instead of awaiting, like so:

    long[] results = Task.WhenAll(tasks).Result;
    
    0 讨论(0)
  • 2021-02-07 22:49

    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);
    
    0 讨论(0)
提交回复
热议问题