问题 调用API时在何处使用并发


在一个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之前)

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

谢谢


9044
2017-07-13 08:02


起源

实际上,这是一个很好的方向。但你忘记了最重要的部分。你需要 等待 结果,例如 等待Task.WhenAll 但是你需要在DeployView函数中添加'async'关键字。你最好深入了解一下 异步/ AWAIT 范例。 - ckruczek
那么你面临的问题/例外是什么?我也同意ckruczek,你正在采取的方向没有错......那么,你想得到答案吗?
我想得到答复,是的。但如果一切都好的话,不确定如何使用它们 - mitomed
“尝试并行整个DeployItem”你的意思是什么? - usr
@usr,我想知道是否要在任务中创建htppclient。对不起,我放了DeployItem,它应该是DeployView - mitomed


答案:


你介绍的是什么 并发不是 排比。更多内容 这里

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

首先,您应该将您的方法标记为 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 而不是每次分配一个,并在您不再需要其服务时处置它。


5
2017-07-13 08:29





平时 没有必要并行化请求 - 一个线程使异步请求应该足够(即使你有数百个请求)。考虑以下代码:

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实例 - 它是线程安全的。


6
2017-07-13 08:28



Async IO不会使IO更快。它与单个IO的速度无关。他可以 只要 通过并行化更快。 - usr
我从未说过异步会使IO更快。发布单个异步请求相对较快(无需等待响应),足够快,一个线程可以立即生成数千个。这就是我强调'通常'的原因。 - shay__


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

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

别忘了 await 该 WhenAll 任务并制定方法 async Task。 (如果你不明白为什么那是必要的,你就会有一些研究 await 去做。)


4
2017-07-13 08:26



感谢您的深刻回答,因为@YuvalItzchakov metnioned我认为在这种情况下保留客户端更有意义,因为它可用于并发请求,因为此方法将被多次调用 - mitomed