在调用API时,何时使用并发?

11

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

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

谢谢


2
其实你的方向是正确的,但你忘了最重要的部分。你需要等待结果,例如await Task.WhenAll,但然后你需要在DeployView函数中添加'async'关键字。你最好深入了解async/await编程范式。 - ckruczek
1
那么你遇到了什么问题/异常?我也同意ckruczek的看法,你所采取的方向没有任何问题...另外,你是否想要得到回应? - user844705
我想要得到回应,是的。但如果它们都是“好”的话,我不确定该如何使用它们。 - mitomed
“尝试并行化整个DeployItem”,这是什么意思? - usr
@usr,我在想是否应该在任务本身中创建htppclient。抱歉,我写错了,应该是DeployView而不是DeployItem。 - mitomed
3个回答

6

通常情况下,不需要并行化请求——一个线程进行异步请求就足够了(即使您有数百个请求)。请考虑以下代码:

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允许Tasks并行(多线程)执行,则不会像第一个示例中那样有任何响应闲置。
*编辑-如果您决定并行执行,可以使用一个HttpClient实例-它是线程安全的。

异步IO并不会使IO更快。它与单个IO的速度无关。只有通过并行化才能使其更快。 - usr
我从未说过异步使IO更快。发布单个异步请求相对较快(无需等待响应),一个线程可以很快地执行成千上万次。这就是我强调“通常”的原因。 - shay__

5
你提到的是并发性而不是并行性。这里有更多相关信息。链接
你的方向是正确的,但我会对其进行一些细微的更改:
首先,你应该将你的方法标记为 "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 实例而不是每次分配一个,并在不再需要其服务时将其释放。


4

HttpClient 看起来可以用于并发请求。 我没有亲自验证过,这只是我从搜索中了解到的。因此,您不必为启动的每个任务创建新的客户端。您可以按照最方便的方式进行操作。

总体而言,我尽量分享尽可能少的(可变)状态。资源获取通常应向其使用内部推进。我认为最好的做法是创建一个帮助器 CreateHttpClient 并在此处为每个请求创建一个新的客户端。考虑将 Select 主体创建为新的异步方法。然后,HttpClient 使用完全隐藏在 DeployView 中。

不要忘记 await WhenAll 任务并使该方法成为 async Task。 (如果您不理解为什么需要这样做,则需要进行关于 await 的一些研究。)


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - mitomed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接