Dentro del proyecto ac# estoy haciendo algunas llamadas a una API web, la cosa es que las estoy haciendo dentro de un bucle en un método. Normalmente no hay tantos pero aunque estaba pensando en aprovechar el paralelismo.
Lo que estoy intentando hasta ahora es
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); } }Pero me pregunto si esa es la ruta correcta, o debería intentar poner en paralelo todo DeployView (es decir, incluso antes de usar HttpClient)
Ahora que lo veo publicado, creo que no puedo simplemente eliminar la respuesta variable también, solo hacer la espera sin configurarla en ninguna variable
Gracias
Por lo general, no es necesario paralelizar las solicitudes: un hilo que realice solicitudes asíncronas debería ser suficiente (incluso si tiene cientos de solicitudes). Considere este código:
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 }Sin embargo, puede utilizar el paralelismo al procesar las respuestas. En lugar del ciclo 'foreach' anterior, puede usar:
Parallel.Foreach(tasks.Select(p=> p.Result), response => ProcessResponse(response));Pero TMO, esta es la mejor utilización de asincrónico y paralelismo:
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);Hay una gran diferencia entre el primer y el último ejemplo: en el primero, tiene un subproceso que inicia solicitudes asincrónicas, espera (sin bloqueo) a que regresen todas y solo luego las procesa. En el segundo ejemplo, adjunta una continuación a cada tarea. De esa manera, cada respuesta se procesa tan pronto como llega. Suponiendo que el TaskScheduler actual permita la ejecución paralela (multiproceso) de tareas, ninguna respuesta permanece inactiva como en el primer ejemplo.
* Editar: si decide hacerlo en paralelo, puede usar solo una instancia de HttpClient: es seguro para subprocesos.
Lo que está introduciendo es concurrencia , no paralelismo . Más sobre eso aquí .
Su dirección es buena, aunque haría algunos cambios menores:
Primero, debe marcar su método como async Task ya que está usando Task.WhenAll , que devuelve un awaitable, que deberá esperar de forma asíncrona. A continuación, simplemente puede devolver la operación desde PostAsJsonAsync , en lugar de esperar cada llamada dentro de su Select . Esto ahorrará un poco de gastos generales, ya que no generará la máquina de estado para la llamada asíncrona:
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 puede realizar solicitudes simultáneas (consulte el enlace @usr para obtener más información), por lo tanto, no veo una razón para crear una nueva instancia cada vez dentro de su lambda. Tenga en cuenta que si DeployViewAsync varias veces, tal vez desee conservar su HttpClient en lugar de asignar uno cada vez y desecharlo una vez que ya no necesite sus servicios.
HttpClient parece ser utilizable para solicitudes simultáneas. No lo he verificado yo mismo, esto es solo lo que deduzco de la búsqueda. Por lo tanto, no tiene que crear un nuevo cliente para cada tarea que esté iniciando. Puedes hacer lo que más te convenga.
En general, me esfuerzo por compartir el menor estado (mutable) posible. Las adquisiciones de recursos generalmente deben impulsarse hacia adentro hacia su uso. Creo que es un mejor estilo crear un asistente CreateHttpClient y crear un nuevo cliente para cada solicitud aquí. Considere convertir el cuerpo de Select en un nuevo método asíncrono. Entonces, el uso de HttpClient está completamente oculto de DeployView .
No olvide await la tarea WhenAll y hacer que el método sea async Task . (Si no entiende por qué es necesario, tiene que investigar un await ).