HttpWebRequest - могу ли я сделать несколько звонков одновременно из нескольких потоков - PullRequest
8 голосов
/ 19 февраля 2011

Я использую HttpWebRequest для создания запросов к веб-страницам, а не для их анализа.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);

тогда, если несколько потоков вызовут

HttpWebResponse response = (HttpWebResponse)request.GetResponse()

одновременно, каждый из них должен получить свой собственный ответ, или для потока 2 возможно получить ответ для thread7, например?

Obs: адрес один и тот же для всех потоков, меняются только параметры POST

 public class CheckHelper
{
    public  string GetPOSTWebsiteResponse(string WebAddress, string year)
    {
        StringBuilder QuerryData = new StringBuilder();
        String ResponseString;
        QuerryData.Append("forYear"+ "=" + year);

        #region build request
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(WebAddress);
        // Set the Method property of the request to POST
        request.Method = "POST";

        NameValueCollection headers = request.Headers;
        Type t = headers.GetType();
        PropertyInfo p = t.GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        p.SetValue(headers, false, null);
        byte[] byteArray = Encoding.UTF8.GetBytes(QuerryData.ToString());
        request.ContentType = "application/x-www-form-urlencoded";
        request.ContentLength = byteArray.Length;

        #endregion

        // Get the request stream.
        using (Stream requestStream = request.GetRequestStream())
        {
            // Write the data to the request stream.
            requestStream.Write(byteArray, 0, byteArray.Length);
            // Close the Stream object.
        }



        #region get response
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            //Get the stream containing content returned by the server.
            using (var responseStream = response.GetResponseStream())
            {
                // Open the stream using a StreamReader for easy access.
                using (StreamReader responseReader = new StreamReader(responseStream))
                {
                    // Read the content.
                    ResponseString = responseReader.ReadToEnd();


                }
            }
        }

        #endregion        
        return ResponseString;
      }
}

вот как я использую метод:

            Dictionary<int, Thread> threads=new Dictionary<int,Thread>();
            foreach (var year in AvailableYears)
            {
                threads[year] = new Thread(delegate()
                    {
                    var client=new CheckHelper(); 
                    string response=client.GetPOSTWebsiteResponse("http://abc123.com", year.ToString())
                    //The thread for year 2003 may get the response for the year 2007

                    responsesDictionary[year]=response;
                    });
                threads[year].Start();

            }
            //this is to force the main thread to wait until all responses are    received:
        foreach(var th in threads.Values){
                th.Join(10000);
            }

Скажитеменя где я ошибаюсь?Как мне изменить код?Пожалуйста, помогите, я не могу найти ничего полезного в Интернете!

Ответы [ 2 ]

18 голосов
/ 19 февраля 2011

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

Идеальная ситуация заключается в том, что у вас асинхронный рабочий процесс.где ваш цикл выглядит примерно так:

GetAsyncRequest MakeAsyncRequest ReceiveResponseAsync ProcessResponse WaitForAllRequestProcessingToComplete (необязательно)

Такой, что результат каждого шага передается следующему (если есть результат) и следующему.И вы обрабатываете ответы, как только получаете их, а не накапливаете (объединяете / блокируете) все ответы, прежде чем приступить к их обработке.Такого рода вещи можно легко сделать с помощью Tasks и ContinueWith в .NET 4.0 и, видя, что вы используете .NET 4.0, я настоятельно рекомендую вам сделать это, как описано выше.

Но, если выневозможно преобразовать вашу обработку в асинхронный рабочий процесс, а затем ...

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

static string GetWebResponse(string url, NameValueCollection parameters)
{
  var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
  httpWebRequest.ContentType = "application/x-www-form-urlencoded";
  httpWebRequest.Method = "POST";

  var sb = new StringBuilder();
  foreach (var key in parameters.AllKeys)
    sb.Append(key + "=" + parameters[key] + "&");
  sb.Length = sb.Length - 1;

  byte[] requestBytes = Encoding.UTF8.GetBytes(sb.ToString());
  httpWebRequest.ContentLength = requestBytes.Length;

  using (var requestStream = httpWebRequest.GetRequestStream())
  {
    requestStream.Write(requestBytes, 0, requestBytes.Length);
    requestStream.Close();
  }

  Task<WebResponse> responseTask = Task.Factory.FromAsync<WebResponse>(httpWebRequest.BeginGetResponse, httpWebRequest.EndGetResponse, null);
  using (var responseStream = responseTask.Result.GetResponseStream())
  {
    var reader = new StreamReader(responseStream);
    return reader.ReadToEnd();
  }
}

Вы бы назвали это так:

  ServicePointManager.DefaultConnectionLimit = 20;//Please test different numbers here
  var tasks = new List<Task<string>>();
  for (int i = 1990; i < 2090; i++)
  {
    var postParameters = new NameValueCollection();
    postParameters.Add("data", i.ToString());
    tasks.Add(Task.Factory.StartNew(() => { return GetWebResponse("http://www.abc123.com", postParameters); }));
  }
  Task.WaitAll(tasks.ToArray());
  //At this point tasks[0].Result will be the result (The Response) of the first task
  //tasks[1].Result will be the result of the second task and so on.

Посмотрите, работает ли это для вас.

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

Кроме того, без настройки DefaultConnectionLimit в ServicePointManager вы никогда не будетеполучить более 2 потоков в любом случае, так как вы идете против одного домена и ограничение по умолчанию составляет 2 потока на домен.

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

Редактировать: при использовании асинхронного ввода-вывода вы не используете рабочие потоки, ноПоток ввода / вывода.Поэтому в основном вы не хотите использовать QueueUserWorkItem (для создания потоков) или сами не создаете потоки.

Код, который я представил, использует асинхронный ввод-вывод, и если вы собираетесь выполнять несколько запросов одновременнои как можно быстрее.

Цикл for (во втором листинге кода) завершится почти сразу, хотя в этом примере он повторяется в течение 100 итераций, а затем будет ожидать завершения всех запросов ввода-вывода.ThreadPool и ОС будут обрабатывать задания ввода-вывода как можно быстрее и быстрее.Фактически, поскольку эти задания связаны с вводом-выводом, вы также не увидите увеличения загрузки вашего ЦП (если только вы не выполняете работу, связанную с процессором позже).

Просто поиграйте с ServiceManager.DefaultConnectionLimit, чтобы получитьбольше ускорения, если это необходимо.Обратите внимание, что это также влияет на службу (сервер), поскольку, если вы делаете много одновременных запросов, сервер, который вы вызываете, сильно загружается, и это может быть не то, что вам нужно.Таким образом, необходимо соблюсти баланс.

После вызова Task.WaitAll вы можете перебирать свою коллекцию задач и получать результаты каждой задачи, используя синтаксис, показанный в строке с комментариями в листинге кода..

0 голосов
/ 19 февраля 2011

Это зависит от того, как вы это делаете.И если вы получаете ответы, инициированные из одного потока в другом, вы делаете это неправильно.

Возможно, хороший способ сделать это - разработать единицу работы, которая будет принимать ваш URL и публиковать информацию какпараметры.Затем он запустит новый экземпляр HttpWebRequest, обработает ответ и передаст этот ответ (с любой необходимой модификацией / очисткой).

Эти единицы работы могут быть затем запущены в отдельных потоках с собранными ответами.После завершения всех потоков вы можете обработать результаты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...