Подтвердить что ты не робот

Где использовать concurrency при вызове API

Внутри проекта С# я делаю некоторые вызовы в веб-api, дело в том, что я делаю их в цикле в методе. Обычно их не так много, но даже при том, что я думал об использовании parallelism.

То, что я пытаюсь сделать до сих пор,

public void DeployView(int itemId, string itemCode, int environmentTypeId)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiUrl"]);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var agents = _agentRepository.GetAgentsByitemId(itemId);

        var tasks = agents.Select(async a =>
            {
                var viewPostRequest = new
                    {
                        AgentId = a.AgentId,
                        itemCode = itemCode,
                        EnvironmentId = environmentTypeId
                    };

                var response = await client.PostAsJsonAsync("api/postView", viewPostRequest);
            });

        Task.WhenAll(tasks);
    }
}

Но задайтесь вопросом, правильно ли этот путь, или я должен попытаться выполнить параллельный весь DeployView (т.е. даже до использования HttpClient)

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

Спасибо

4b9b3361

Ответ 1

Что вы вводите, это concurrency, а не parallelism. Подробнее об этом здесь.

Ваше направление хорошее, хотя несколько незначительных изменений, которые я бы сделал:

Во-первых, вы должны пометить свой метод как async Task, поскольку вы используете Task.WhenAll, который возвращает ожидаемый, который вам нужно будет асинхронно ждать. Затем вы можете просто вернуть операцию из PostAsJsonAsync, а не ждать каждого вызова внутри вашего Select. Это сэкономит немного накладных расходов, так как оно не будет генерировать машину состояний для асинхронного вызова:

public async Task DeployViewAsync(int itemId, string itemCode, int environmentTypeId)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiUrl"]);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
                   new MediaTypeWithQualityHeaderValue("application/json"));

        var agents = _agentRepository.GetAgentsByitemId(itemId);
        var agentTasks = agents.Select(a =>
        {
            var viewPostRequest = new
            {
                AgentId = a.AgentId,
                itemCode = itemCode,
                EnvironmentId = environmentTypeId
            };

            return client.PostAsJsonAsync("api/postView", viewPostRequest);
        });

        await Task.WhenAll(agentTasks);
    }
}

HttpClient может выполнять параллельные запросы (см. ссылку @usr для большего), поэтому я не вижу причины создавать новый экземпляр каждый раз внутри вашей лямбды. Обратите внимание: если вы потребляете DeployViewAsync несколько раз, возможно, вам захочется сохранить ваш HttpClient, а не выделять его каждый раз, и удалять его, когда вам больше не нужны его службы.

Ответ 2

Обычно нет необходимости распараллеливать запросы - один поток, делающий асинхронные запросы, должен быть достаточным (даже если у вас есть сотни запросов). Рассмотрим этот код:

var tasks = agents.Select(a =>
        {
            var viewPostRequest = new
                {
                    AgentId = a.AgentId,
                    itemCode = itemCode,
                    EnvironmentId = environmentTypeId
                };

            return client.PostAsJsonAsync("api/postView", viewPostRequest);
        });
    //now tasks is IEnumerable<Task<WebResponse>>
    await Task.WhenAll(tasks);
    //now all the responses are available
    foreach(WebResponse response in tasks.Select(p=> p.Result))
    {
        //do something with the response
    }

Однако при обработке ответов вы можете использовать parallelism. Вместо вышеуказанного цикла foreach вы можете использовать:

Parallel.Foreach(tasks.Select(p=> p.Result), response => ProcessResponse(response));

Но TMO, это лучшее использование асинхронных и parallelism:

var tasks = agents.Select(async a =>
        {
            var viewPostRequest = new
                {
                    AgentId = a.AgentId,
                    itemCode = itemCode,
                    EnvironmentId = environmentTypeId
                };

            var response = await client.PostAsJsonAsync("api/postView", viewPostRequest);
            ProcessResponse(response);
        });
await Task.WhenAll(tasks);   

Существует существенное различие между первым и последним примерами: В первом случае у вас есть один поток, запускающий асинхронные запросы, ждущий (не блокирующий) для всех из них для возврата, и только затем обрабатывая их. Во втором примере вы присоединяете продолжение к каждой Задаче. Таким образом, каждый ответ обрабатывается сразу же после его поступления. Предполагая, что текущий TaskScheduler допускает параллельное (многопоточное) выполнение Заданий, никакой ответ остается бездействующим, как в первом примере.

* Редактировать - если вы сделаете решили сделать это параллельно, вы можете использовать только один экземпляр HttpClient - это безопасный поток.

Ответ 3

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

В общем, я стараюсь делиться как можно меньшим (изменчивым) состоянием. Приобретения ресурсов обычно следует вводить внутрь к их использованию. Я считаю, что лучше создать хелпер CreateHttpClient и создать новый клиент для каждого запроса здесь. Рассмотрим создание тела Select нового метода асинхронизации. Затем использование HttpClient полностью скрыто от DeployView.

Не забывайте await задачу WhenAll и создайте метод async Task. (Если вы не понимаете, почему это необходимо, вам нужно провести исследование await).