Где использовать 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)
Теперь, когда я вижу, что он опубликован, я считаю, что не могу просто удалить ответ переменной, просто сделайте ожидание, не устанавливая его для какой-либо переменной
Спасибо
Ответы
Ответ 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
).