Выполнить x количество веб-запросов одновременно - PullRequest
3 голосов
/ 24 октября 2011

В нашей компании есть веб-сервис, который я хочу отправлять XML-файлы (хранящиеся на моем диске) через мой собственный клиент HTTPWebRequest в C #. Это уже работает. Веб-служба поддерживает 5 синхронных запросов одновременно (я получаю ответ от веб-службы после завершения обработки на сервере). Обработка занимает около 5 минут для каждого запроса.

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

Прямо сейчас мой Webrequest клиент отправит XML и будет ждать ответа, используя result.AsyncWaitHandle.WaitOne();

Однако, таким образом, одновременно может обрабатываться только один запрос, хотя веб-служба поддерживает 5. Я пытался использовать Backgroundworker и Threadpool, но они создают слишком много запросов одновременно, что делает их бесполезными для меня , Любое предложение, как можно решить эту проблему? Создать свой собственный Threadpool с ровно 5 нитями? Есть предложения, как это реализовать?

Ответы [ 4 ]

1 голос
/ 25 октября 2011

Если вы работаете в .Net 4, это выглядит как идеально подходящее для Parallel.ForEach(). Вы можете установить MaxDegreeOfParallelism, что означает, что вы гарантированно не обрабатываете больше элементов за один раз.

Parallel.ForEach(items,
                 new ParallelOptions { MaxDegreeOfParallelism = 5 },
                 ProcessItem);

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

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

Самый простой способ - создать 5 потоков (кроме: это нечетное число!), Которые будут использовать XML-файлы из BlockingCollection.

Что-то вроде:

var bc = new BlockingCollection<string>();

for ( int i = 0 ; i < 5 ; i++ )
{
    new Thread( () =>
        {
            foreach ( var xml in bc.GetConsumingEnumerable() )
            {
                // do work
            }
        }
    ).Start();
}

bc.Add( xml_1 );
bc.Add( xml_2 );
...
bc.CompleteAdding(); // threads will end when queue is exhausted
0 голосов
/ 25 октября 2011

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

static void SpawnThreads(int count, Action action)
{
    var countdown = new CountdownEvent(count);

    for (int i = 0; i < count; i++)
    {
        new Thread(() =>
        {
            action();
            countdown.Signal();
        }).Start();
    }

    countdown.Wait();
}

И затем использовать BlockingCollection<string> (потокобезопасная коллекция), чтобы отслеживать ваши файлы XML.Используя описанный выше вспомогательный метод, вы могли бы написать что-то вроде:

static void Main(string[] args)
{
    var xmlFiles = new BlockingCollection<string>();

    // Add some xml files....

    SpawnThreads(5, () =>
    {
        using (var web = new WebClient())
        {
            web.UploadFile(xmlFiles.Take());
        }
    });

    Console.WriteLine("Done");
    Console.ReadKey();
}

Обновление

Еще лучшим подходом будет загрузка файлов асинхронно, так что выне тратьте ресурсы на использование потоков для задачи ввода-вывода.

Опять же, вы можете написать вспомогательный метод:

static void SpawnAsyncs(int count, Action<CountdownEvent> action)
{
    var countdown = new CountdownEvent(count);

    for (int i = 0; i < count; i++)
    {
        action(countdown);
    }

    countdown.Wait();
}

И использовать его как:

static void Main(string[] args)
{
    var urlXML = new BlockingCollection<Tuple<string, string>>();
    urlXML.Add(Tuple.Create("http://someurl.com", "filename"));

    // Add some more to collection...

    SpawnAsyncs(5, c =>
    {
        using (var web = new WebClient())
        {
            var current = urlXML.Take();

            web.UploadFileCompleted += (s, e) =>
            {
                // some code to mess with e.Result (response)
                c.Signal();
            };

            web.UploadFileAsyncAsync(new Uri(current.Item1), current.Item2);
        }
    });

    Console.WriteLine("Done");
    Console.ReadKey();
}
0 голосов
/ 24 октября 2011

Создание собственного пула потоков из пяти потоков не сложно - просто создайте параллельную очередь объектов, описывающих запрос, и сделайте пять потоков, которые циклически выполняют задачу по мере необходимости. Добавьте событие AutoResetEvent, и вы сможете убедиться, что они не вращаются неистово, пока нет запросов, требующих обработки.

Однако может быть сложно вернуть ответ правильному вызывающему потоку. Если это так, как работает остальная часть вашего кода, я бы использовал другой подход и создал бы ограничитель, который немного напоминает монитор, но допускает 5 одновременных потоков, а не только один:

private static class RequestLimiter
{
  private static AutoResetEvent _are = new AutoResetEvent(false);
  private static int _reqCnt = 0;
  public ResponseObject DoRequest(RequestObject req)
  {
    for(;;)
    {
      if(Interlocked.Increment(ref _reqCnt) <= 5)
      {
        //code to create response object "resp".
        Interlocked.Decrement(ref _reqCnt);
        _are.Set();
        return resp;
      }
      else
      {
          if(Interlocked.Decrement(ref _reqCnt) >= 5)//test so we don't end up waiting due to race on decrementing from finished thread.
           _are.WaitOne();
      }
    }
  }
}
...