调用API时在何处使用并发性
本文关键字:并发 在何处 API 调用 | 更新日期: 2023-09-27 18:02:32
在一个c#项目中,我正在对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之前)
现在我看到它发布了,我想我不能只是删除变量响应,只是做等待而不将其设置为任何变量
谢谢
通常不需要并行化请求-一个线程发出异步请求应该足够了(即使您有数百个请求)。考虑以下代码:
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
}
但是,您可以在处理响应时利用并行性。而不是上面的'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);
第一个和最后一个例子有一个主要的区别:在第一个示例中,您有一个线程启动异步请求,等待(非阻塞)所有返回,然后才处理它们。在第二个示例中,您将延续附加到每个Task。这样,每个响应一到就能得到处理。假设当前的TaskScheduler允许并行(多线程)执行任务,那么在第一个示例中没有空闲响应。
*编辑-如果你做决定并行,你可以只使用一个实例的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 link),因此我认为没有理由每次都在lambda中创建一个新实例。请注意,如果多次使用DeployViewAsync
,也许您希望保留HttpClient
,而不是每次分配一个,并在不再需要它的服务时处理它。
HttpClient
似乎可以用于并发请求。我自己没有证实过,这只是我从搜索中收集到的。因此,您不必为正在启动的每个任务创建一个新客户机。你可以做你最方便的事。
一般来说,我努力尽可能少地共享(可变的)状态。资源的获取通常应该向内推进,以便使用它们。我认为在这里创建一个助手CreateHttpClient
并为每个请求创建一个新客户端是更好的风格。考虑将Select
主体设置为一个新的异步方法。然后,HttpClient
的用法对DeployView
是完全隐藏的。
不要忘记await
WhenAll
任务和async Task
方法。(如果你不明白为什么这是必要的,你有一些关于await
的研究要做。)