Синхронное параллельное использование HttpClient

Я пытаюсь использовать HttpClient синхронно, но когда я делаю много одновременных запросов, он перестает работать. Я написал два теста, один для асинхронного использования, а другой для синхронного. TestMethod всегда возвращает ответ через 4 секунды. Асинхронный тест работает нормально. Почти все запросы в синхронном тесте истекают по тайм-ауту, только ~20 последних запросов успешны. Я пробовал как использовать один HttpClient для запросов, так и создавать новый экземпляр HttpClient для каждого нового запроса. Нет разницы. Возможно, это как-то связано с этим тупиком .

Я использую VS2013 с .NET Framework 4.5.1, ориентированным на Framework 4.5. Я получаю HttpClient через NuGet: <package id="Microsoft.Net.Http" version="2.2.15" targetFramework="net45" />

Я пока не хочу использовать HttpClient асинхронно, потому что это означает, что мне нужно переписать все приложение. Любые идеи, что я делаю неправильно здесь?

// all 800 requests complete successfully
[Test]
public async void Asyncmethod()
{
    var sw = new Stopwatch();
    sw.Start();

    var client = new HttpClient();
    client.Timeout = TimeSpan.FromSeconds(15);

    var tasks = Enumerable.Range(0, 800).Select(x => Task.Run(async () =>
    {
        try
        {
            var swx = new Stopwatch();
            swx.Start();
            var response = await client.GetStringAsync("http://localhost:7002/TestMethod").ConfigureAwait(false);
            swx.Stop();
            Console.WriteLine(x + " " + response + " in " + swx.ElapsedMilliseconds + " ms.");
        }
        catch (Exception e)
        {
            Console.WriteLine(x + " Exception: " + e.Message);
        }
    })).ToArray();

    await Task.WhenAll(tasks);

    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
}

// almost all of 800 requests time out
[Test]
public void Syncmethod()
{
    var sw = new Stopwatch();
    sw.Start();

    var client = new HttpClient();

    var tasks = Enumerable.Range(0, 800).Select(x => Task.Run(() =>
    {
        try
        {
            var swx = new Stopwatch();
            swx.Start();
            var response = client.GetStringAsync("http://localhost:7002/TestMethod");
            if (response.Wait(15000))
            {
                swx.Stop();
                Console.WriteLine(x + " " + response.Result + " in " + swx.ElapsedMilliseconds + " ms.");
            }
            else
            {
                swx.Stop();
                Console.WriteLine(x + " timed out.");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(x + " Exception: " + e.Message);
        }
    })).ToArray();

    foreach (var task in tasks)
        task.Wait(60000);

    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
}

person Thresh    schedule 22.10.2013    source источник


Ответы (2)


Я рекомендую вам использовать await для HttpClient вместо Wait. Если вам действительно нужны синхронные запросы, рассмотрите возможность использования WebClient, который поддерживает синхронные методы.

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

person Stephen Cleary    schedule 22.10.2013
comment
await - ведь вопрос помечен тегом C#-5.0 - person Paulo Morgado; 23.10.2013
comment
Проблема в том, что если нет тайм-аута, ни один из запросов не выполняется. Я получаю около 20 завершенных запросов только после того, как 780 отменены из-за тайм-аута. - person Thresh; 23.10.2013
comment
Использование await не вариант, мне придется переписать и все остальное. Да, WebClient работает отлично. Но я до сих пор не понимаю, почему HttpClient зависает. - person Thresh; 23.10.2013
comment
DefaultConnectionLimit — это ОЧЕНЬ веская причина, чтобы повеситься. Значение по умолчанию — 2, что означает, что даже 20 выполненных запросов — это неплохо. Просто увеличьте лимит - person Panagiotis Kanavos; 01.11.2013

Да, это похоже на тупик.

Этот код работает идеально

public static void Syncmethod()
    {
        var sw = new Stopwatch();
        sw.Start();

        var client = new HttpClient();

        var tasks = Enumerable.Range(0, 800).Select(x => Task.Run(() =>
        {

            var swx = new Stopwatch();
            swx.Start();
            var result =
            client.GetStringAsync("http://yandex.ru").ContinueWith(task =>
            {
                try
                {
                    swx.Stop();
                    Console.WriteLine(x + " " + task.Result + " in " + swx.ElapsedMilliseconds + " ms.");
                    return task.Result;
                }
                catch (Exception e)
                {
                    swx.Stop();
                    Console.WriteLine(x + " Exception: " + e.Message);
                    throw e;
                }
            }, TaskContinuationOptions.AttachedToParent);

        })).ToArray();

        foreach (var task in tasks)
            task.Wait(60000);

        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }

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

Посмотрите на этот пост HttpClient.GetAsync(...) никогда не возвращается при использовании await/async

И, как описано выше, вы можете повесить свой асинхронный пример простым изменением

var response = client.GetStringAsync("http://yandex.ru").GetAwaiter().GetResult();

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

person sh1ng    schedule 22.10.2013
comment
Прежде всего, в вашем примере задача будет завершена до завершения запроса, если запрос достаточно длинный. Во-вторых, как мне написать функцию, которая возвращает что-то из ответа, используя ContinueWith? - person Thresh; 23.10.2013
comment
Обновлено. Но вам придется переписать свой код, чтобы использовать Task‹string› без ожидания. - person sh1ng; 23.10.2013