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

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

谢谢

调用API时在何处使用并发性

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

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的研究要做。)