调用API时在何处使用并发

在ac #project项目中我正在调用一个web api,问题是我在一个方法的循环中做它们。 通常没有那么多,但即使我正在考虑利用并行性。

到目前为止我正在尝试的是

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之前)

现在我看到它发布了,我认为我不能只删除变量响应,只需执行等待而不将其设置为任何变量

谢谢

你所介绍的是并发性 ,而不是并行性 。 更多关于这一点 。

你的方向很好,虽然我会做一些小改动:

首先,你应该将你的方法标记为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链接了解更多信息),因此我没有看到每次在lambda中创建新实例的理由。 请注意,如果多次使用DeployViewAsync ,可能需要保留HttpClient ,而不是每次都分配一个HttpClient ,并在不再需要其服务时将其丢弃。

通常不需要并行化请求 – 一个线程使异步请求足够(​​即使您有数百个请求)。 考虑以下代码:

 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> await Task.WhenAll(tasks); //now all the responses are available foreach(WebResponse response in tasks.Select(p=> p.Result)) { //do something with the response } 

但是,您可以在处理响应时使用并行性。 您可以使用以下“foreach”循环代替上述“foreach”循环:

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

但TMO,这是异步和并行的最佳利用:

 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允许并行(multithreading)执行任务,则没有响应像第一个示例那样保持空闲状态。

*编辑 – 如果您决定并行执行,您只能使用一个HttpClient实例 – 它是线程安全的。

HttpClient似乎可用于并发请求。 我自己没有证实这一点,这正是我从搜索中收集的内容。 因此,您不必为要启动的每个任务创建新客户端。 你可以做最方便的事情。

总的来说,我努力尽可能少地分享(可变)状态。 通常应该将资源获取推向其使用范围。 我认为创建帮助程序CreateHttpClient并为此处的每个请求创建一个新客户端是更好的方式。 考虑使Select body成为一种新的异步方法。 然后,从DeployView完全隐藏HttpClient用法。

不要忘记await WhenAll任务并使方法成为async Task 。 (如果你不明白为什么这是必要的,你就会有一些关于await研究。)