Надеюсь увеличить скорость моего WebService с асинхронными вызовами, но не знаю, как - PullRequest
2 голосов
/ 23 сентября 2011

Я пишу веб-сервис, который использует сторонний веб-сервис и значительно расширяет функциональность. Например, в одной части рабочего процесса я должен циклически просмотреть результаты одного вызова API, и для каждого результата сделать еще один вызов API, чтобы вернуть результаты, которые фактически можно использовать. В настоящее время это приводит к примерно 7500 строкам XML, а также к времени загрузки 3-4 минуты. (Конечно, это время загрузки основано на запуске WebService в режиме отладки из Visual Studio на дрянном ПК с дрянным подключением к Интернету, и я ожидайте, что он будет немного более быстрым при запуске с высокопроизводительного сервера Windows). Я хотел бы найти способ порождать новый поток Asyncronous для каждого вызова API (чтобы каждой итерации не приходилось ждать окончания предыдущей итерации), но я не уверен, как это сделать, и до сих пор иметь возможность возвращать вывод XML в том же вызове функции. Есть идеи?

:: EDIT :: - Вот код, с помощью которого я генерирую свой XML. Обратите внимание, что все вызовы функций являются просто оболочками вызовов API для сторонних API.

public List<AvailabilityList> getResortsForDate(String month, int year) {
        List<RegionList> regions = this.getRegionLists( );
        List<AvailabilityList> availability = new List<AvailabilityList>();
        foreach(RegionList parent in regions)
        {
            foreach(Region child in parent.Regions)
            {
                if (!String.Equals(child.ID, "?"))
                {
                    int countryID = Int32.Parse(parent.CountryID);
                    AvailabilityList current = this.getExchangeAvailability(countryID, month, year, child.ID);
                    if (current.ListCount != 0)
                    {
                        availability.Add(current);
                    }
                }
            }
        }
        return availability;
    }

:: РЕДАКТИРОВАТЬ # 2 :: РЕШЕНИЕ! Это решение, которое я использовал в конечном итоге, является незначительной корректировкой выбранного мной ответа. Спасибо! После синхронизации моего предыдущего кода (5 минут и 1 секунда) этот код стал огромным улучшением за 1 минуту и ​​6 секунд, причем 30 секунд времени принадлежат другому методу, который я также буду оптимизировать!

public List<AvailabilityList> _asyncGetResortsForDate(String month, int year) {
        List<RegionList> regions = this.getRegionLists();
        List<AvailabilityList> availability = new List<AvailabilityList>();

        List<WaitHandle> handles = new List<WaitHandle>();
        List<AvailabilityList> _asyncResults = new List<AvailabilityList>();

        regions.ForEach(parent => {
            parent.Regions.ForEach(child => {
                    if (!String.Equals(child.ID, "?")) {
                        int countryID = Int32.Parse(parent.CountryID);

                        Func<AvailabilityList> _getList = () => this.getExchangeAvailability(countryID, month, year, child.ID);

                        IAsyncResult res = _getList.BeginInvoke(new AsyncCallback(
                            x => {
                                AvailabilityList result = (x.AsyncState as Func<AvailabilityList>).EndInvoke(x);
                                if (result.ListCount > 0)
                                {
                                    _asyncResults.Add(result);
                                }

                            }), _getList);
                        while (handles.Count >= 60)
                        {
                            int item = WaitHandle.WaitAny(handles.ToArray(  ));
                            handles.RemoveAt(item);
                        }
                        handles.Add(res.AsyncWaitHandle);
                    }
                });
        });

        WaitHandle.WaitAll(handles.ToArray());

        return _asyncResults;
    }

Ответы [ 2 ]

1 голос
/ 24 сентября 2011

Разбираться с массивами дескрипторов ожидания, как это, является признаком того, что в вашем коде есть что-то слишком сложное. Вы можете сделать намного более чистую работу с параллельной библиотекой задач.

Например:

public List<AvailabilityList> _asyncGetResortsForDate(String month, int year)
{
    List<RegionList> regions = this.getRegionLists();
    List<AvailabilityList> availability = new List<AvailabilityList>();

    List<Task> tasks = new List<Task>(); 
    List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 

    regions.ForEach(parent =>
    {
        parent.Regions.ForEach(child =>
        {
             if (!String.Equals(child.ID, "?"))
             {
                 int countryID = Int32.Parse(parent.CountryID);
                 var childId = child.ID;

                 Task t = Task.Factory.StartNew((s) =>
                     {
                         var rslt = getExchangeAvailability(countryId, month, year, childId);
                         lock (_asyncResults)
                         {
                             _asyncResults.Add(rslt);
                         }
                      });
                 tasks.Add(t);
             }
        }); 
    });

    Task.WaitAll(tasks);

    return _asyncResults; 
}

(Я не пытался это скомпилировать, но вы поняли суть идеи.)

Пусть TPL беспокоится о предельном значении ожидания 64.

Также обратите внимание, что в вашем коде была ошибка, ожидающая своего появления. Поскольку несколько задач могут пытаться добавить результаты в список _asyncResults, вы должны защитить его с помощью блокировки. List<T>.Add не является потокобезопасным. Если два потока попытаются получить к нему доступ одновременно, вы получите либо поврежденные данные, либо исключение.

Выше также может быть быстрее. Я не уверен, что произойдет, если вы начнете несколько асинхронных вызовов. Вполне вероятно, что пул потоков создаст для них максимальное количество потоков и запустит их все. Вы можете получить 25 или более запущенных потоков с соответствующими переключателями контекста и т. Д. TPL, с другой стороны, намного умнее в использовании потоков. Это создаст меньше параллельных потоков, что позволит избежать большого количества переключений контекста.

Вы можете полностью избежать блокировки, если используете Task<List<AvailabilityList>>. Ваш код становится примерно таким:

Task<List<AvailabilityList>> t = Task<List<AvailabilityList>>.Factory.StartNew((s) =>
    {
        return getExchangeAvailability(countryId, month, year, childId);
    }

А потом, после вашего Task.WaitAll(tasks):

foreach (var t in tasks)
{
    _asyncResults.Add(t.Result);
}

Фактически, вы можете избавиться от Task.WaitAll(tasks), так как Task<T>.Result блокирует, пока не станет доступен результат.

0 голосов
/ 23 сентября 2011

Вот один из способов сделать это асинхронно, когда каждый раз, когда вы вызываете getExchangeAvailability (), он делает это в отдельном потоке, а затем ожидает завершения всех потоков, прежде чем вернуть окончательный список.

    public List<AvailabilityList> _asyncGetResortsForDate(String month, int year)
    {
        List<RegionList> regions = this.getRegionLists();
        List<AvailabilityList> availability = new List<AvailabilityList>();

        List<WaitHandle> handles = new List<WaitHandle>(); 
        List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 

        regions.ForEach(parent =>
        {
            parent.Regions.ForEach(child =>
            {
                 if (!String.Equals(child.ID, "?"))
                 {
                     int countryID = Int32.Parse(parent.CountryID);

                     Func<AvailabilityList> _getList = () => 
                       this.getExchangeAvailability(countryID, month, year, child.ID);

                     IAsyncResult res = _getList.BeginInvoke(new AsyncCallback(
                       x =>
                       {
                          AvailabilityList result = 
                            (x.AsyncState as Func<AvailabilityList>).EndInvoke(x);

                          _asyncResults.Add(result);

                       }), _getList);

                     handles.Add(res.AsyncWaitHandle); 
                 }
            }); 
        });

        WaitHandle.WaitAll(handles.ToArray());

        return _asyncResults; 
    }

Имейте в виду, однако, что если число итераций превышает 64, а максимальное число одновременных потоков по умолчанию (с использованием BeginInvoke ()) равно 64, то после этого момента вы не будете обрабатывать что-либо асинхронно до тех пор, пока один из 64 уже запущенные темы становятся свободными. Также могут быть или не быть некоторые издержки при переключении контекста между потоками. Одна вещь, которую вы, возможно, захотите проверить, это то, сколько времени занимает каждый вызов API, чтобы увидеть, действительно ли это того стоит.

РЕДАКТИРОВАТЬ - Что касается ошибки ограничения 64 потока, я бы предложил две вещи,

1) Вы должны сгруппировать асинхронные вызовы так, чтобы только каждый «родитель» выполнялся в своем собственном потоке, а не каждый дочерний элемент. Это должно уменьшить количество потоков, что-то вроде:

    public List<AvailabilityList> _getAllChildren(RegionList parent, string month, int year)
    {
        List<AvailabilityList> list = new List<AvailabilityList>(); 

        parent.Regions.ForEach(child =>
        {
            if (!String.Equals(child.ID, "?"))
            {
                int countryID = Int32.Parse(parent.CountryID);

                AvailabilityList result = this.getExchangeAvailability(countryID, month, year, child.ID);

                list.Add(result); 
            }
        });

        return list; 
    }

    public List<AvailabilityList> _asyncGetResortsForDate(String month, int year)
    {
        List<RegionList> regions = this.getRegionLists();
        List<AvailabilityList> availability = new List<AvailabilityList>();
        List<WaitHandle> handles = new List<WaitHandle>();

        List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 

        regions.ForEach(parent =>
        {
            Func<List<AvailabilityList>> allChildren = () => _getAllChildren(parent, month, year);

            IAsyncResult res = allChildren.BeginInvoke(new AsyncCallback(
                      x =>
                      {
                          List<AvailabilityList> result =
                            (x.AsyncState as Func<List<AvailabilityList>>).EndInvoke(x);

                          _asyncResults.AddRange(result);

                      }), allChildren);

            handles.Add(res.AsyncWaitHandle); 
        });

        WaitHandle.WaitAll(handles.ToArray());

        return _asyncResults; 
    }

2) Вам может потребоваться добавить проверку перед добавлением в список WaitHandles, чтобы проверить, не рискуете ли вы превысить 64 потока:

     var asyncHandle = res.AsyncWaitHandle; 

     if (handles.Count >= 64)
           asyncHandle.WaitOne();  // wait for this one now
     else if (handles.Count < 64)
           handles.Add(asyncHandle); 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...