Лучший способ назвать многие веб-сервисы?

У меня есть 30 дочерних компаний, и каждый из них реализовал свой веб-сервис (с различными технологиями).

Мне нужно внедрить веб-службу для их агрегирования, например, все веб-службы суб-компании имеют веб-метод с именем GetUserPoint(int nationalCode), и мне нужно реализовать мой веб-сервис, который будет вызывать все из них и собирать все ответы (например, сумма баллов).

Это мой базовый класс:

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

Для каждой из веб-служб субпартнеров я реализую класс, который наследует этот базовый класс и определяет его собственный метод GetPoint.

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

к

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

Итак, это мой веб-метод:

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

ОК, он работает, но он настолько медленный, потому что каждый веб-сервис sub companys размещен на разных серверах (в Интернете).

Я могу использовать параллельное программирование следующим образом: (это называется параллельным программированием!?)

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

Я думаю, что параллельное программирование (и потоковая передача) не подходит для этого решения, потому что мое решение связано с IO (а не с интенсивностью процессора)!

Вызывать все внешние веб-службы настолько медленно, правда? Многие потоки, ожидающие ответа!

Я думаю, что асинхронное программирование - лучший способ, но я новичок в асинхронном программировании и параллельном программировании.

Каков наилучший способ? (parallel.foreach - async TAP - async APM - асинхронное EAP -прохождение)

Пожалуйста, напишите мне пример.

Ответ 1

Это освежает, чтобы увидеть кого-то, кто сделал домашнее задание.

Прежде всего, с .NET 4 (и это все еще очень актуально) TAP является предпочтительной технологией для async workflow в .NET. Задачи легко скомпонованы, и для вас параллелизировать вызовы веб-сервисов - это легкий ветерок, если они предоставляют истинные Task<T> -отверждающиеся API. На данный момент вы "подделали" его с помощью Task.Run, и пока это может быть достаточно для ваших целей. Конечно, ваши потоки потоков потоков будут тратить много времени на блокировку, но если загрузка сервера не очень высока, вы можете с ней справиться, даже если это не идеальная вещь.

Вам просто нужно исправить потенциальное состояние гонки в вашем коде (подробнее об этом ближе к концу).

Если вы хотите следовать лучшим практикам, вы идете с истинным TAP. Если ваши API предоставляют Task -отверждающиеся методы из коробки, это легко. Если нет, это не игра, так как APM и EAP могут быть легко преобразованы в TAP. Ссылка MSDN: https://msdn.microsoft.com/en-us/library/hh873178(v=vs.110).aspx

Здесь также будут приведены примеры конверсий.

APM (взято из другого вопроса SO):

MessageQueue не предоставляет метод ReceiveAsync, но мы можем заставить его играть мяч через 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);

Если у ваших прокси-серверов веб-служб есть методы BeginXXX/EndXXX, это путь.

EAP

Предположим, у вас есть старый прокси веб-службы, полученный из SoapHttpClientProtocol, с использованием только методов async на основе событий. Вы можете преобразовать их в TAP следующим образом:

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

Избегайте гонок при агрегировании результатов

Что касается объединения параллельных результатов, то ваш код цикла TAP почти прав, но вам нужно избегать мутирования общего состояния внутри ваших тел Task, поскольку они, скорее всего, будут выполняться параллельно. Общее состояние Result в вашем случае - это какая-то коллекция. Если эта коллекция не является потокобезопасной (т.е. Если она простая List<long>), то у вас есть условие гонки, и вы можете получить исключения и/или отбросить результаты на Add (я предполагаю AddRange в вашем коде была опечаткой, но если нет - выше все еще применяется).

Простой асинхронный переписать, который фиксирует вашу расу, будет выглядеть так:

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

Если вы решили быть ленивым и придерживаться решения Task.Run на данный момент, исправленная версия будет выглядеть так:

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

Ответ 2

Вы можете создать асинхронную версию 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);
    }
}

Затем собирайте задачи для каждого вызова клиента. После этого выполните все задачи, используя Task.WhenAll. Это выполнит их все параллельно. Кроме того, как отметил Кирилл, вы можете ждать результатов каждой задачи:

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

Если вы не хотите использовать метод агрегирования async, вы можете собирать результаты, вызывая .Result вместо ожидания:

long[] results = Task.WhenAll(tasks).Result;