调用 API 时在哪里使用并发
Posted
技术标签:
【中文标题】调用 API 时在哪里使用并发【英文标题】:Where to use concurrency when calling an API 【发布时间】:2015-07-13 08:02:37 【问题描述】:在 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 之前)
现在我看到它发布了,我认为我不能只删除变量响应,只需执行等待而不将其设置为任何变量
谢谢
【问题讨论】:
其实这是一个很好的方向。但是你忘记了最重要的部分。您需要 await 结果,例如await Task.WhenAll 但您需要将“async”关键字添加到您的 DeployView 函数中。您最好深入了解async/await 范式。 那么您面临的问题/异常是什么?我也同意 ckruczek 的观点,你所采取的方向没有错……另外,你想得到回应吗? 我想得到答复,是的。但是如果它们都可以,不知道如何使用它们 "尝试并行整个 DeployItem" 什么意思? @usr,我想知道是否重点是在任务本身中创建 htppclient。抱歉,我放了 DeployItem,它应该是 DeployView 【参考方案1】:通常不需要并行化请求 - 一个线程发出异步请求就足够了(即使您有数百个请求)。考虑这段代码:
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);
第一个示例和最后一个示例之间有一个主要区别: 在第一个中,您有一个线程启动异步请求,等待(非阻塞)所有返回,然后才处理它们。 在第二个示例中,您将延续附加到每个任务。这样,每个响应一到达就会得到处理。假设当前的 TaskScheduler 允许并行(多线程)执行任务,则不会像第一个示例中那样保持空闲状态。
*编辑 - 如果您做决定并行执行,您可以只使用一个 HttpClient 实例 - 它是线程安全的。
【讨论】:
异步 IO 不会使 IO 更快。它与单个 IO 的速度无关。他只能通过并行化更快。 我从来没有说过异步使 IO 更快。发布单个异步请求相对较快(无需等待响应),速度足以让一个线程立即发出数千个这样的请求。这就是为什么我强调“通常”。【参考方案2】:您介绍的是并发,而不是并行。更多关于 here.
你的方向很好,虽然我会做一些小改动:
首先,您应该将您的方法标记为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
,而不是每次都分配一个,并在您不再需要它的服务时将其丢弃。
【讨论】:
【参考方案3】:HttpClient
appears to be usable for concurrent requests.这个我自己没有验证过,这只是我从搜索中收集到的。因此,您不必为要开始的每个任务都创建一个新客户端。你可以做你最方便的事情。
一般来说,我努力共享尽可能少的(可变)状态。资源获取通常应向内推向其使用。我认为创建一个助手CreateHttpClient
并在此处为每个请求创建一个新客户端会更好。考虑将 Select
主体设为新的异步方法。然后,HttpClient
的用法对DeployView
完全隐藏。
不要忘记await
WhenAll
任务并创建方法async Task
。 (如果您不明白为什么有必要这样做,您可以对 await
进行一些研究。)
【讨论】:
感谢您富有洞察力的回答,正如@YuvalItzchakov 所言,我认为在这种情况下保留客户端对我来说更有意义,因为它可用于并发请求,因为此方法将被多次调用以上是关于调用 API 时在哪里使用并发的主要内容,如果未能解决你的问题,请参考以下文章
Android Studio - 调试 android 应用程序时在哪里可以看到调用堆栈?
使用 fetch API 时在 AJAX 调用期间显示微调器
每次响应 401 Unauthorized 时在 DEV Env 中调用休息,PROD 工作正常
FLutter:迭代列表时在 null 上调用了方法“[]”