在一个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之前)
现在我看到它发布了,我认为我不能只删除变量响应,只需执行等待而不将其设置为任何变量
谢谢
你介绍的是什么 并发不是 排比。更多内容 这里。
你的方向很好,虽然我会做一些小改动:
首先,您应该将您的方法标记为 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
而不是每次分配一个,并在您不再需要其服务时处置它。
平时 没有必要并行化请求 - 一个线程使异步请求应该足够(即使你有数百个请求)。考虑以下代码:
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”循环代替上述“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允许并行(多线程)执行任务,则没有响应像第一个示例那样保持空闲状态。
*编辑 - 如果你 做 决定并行执行,你可以只使用一个HttpClient实例 - 它是线程安全的。
HttpClient
似乎可用于并发请求。 我自己没有证实这一点,这正是我从搜索中收集的内容。因此,您不必为要启动的每个任务创建新客户端。你可以做最方便的事情。
总的来说,我努力尽可能少地分享(可变)状态。通常应该将资源获取推向其使用范围。我认为创建帮助器的风格更好 CreateHttpClient
并在此处为每个请求创建一个新客户端。考虑制作 Select
身体一个新的异步方法。然后, HttpClient
用法完全隐藏起来 DeployView
。
别忘了 await
该 WhenAll
任务并制定方法 async Task
。 (如果你不明白为什么那是必要的,你就会有一些研究 await
去做。)