调用API时在何处使用并发

mit*_*med 11 c# task-parallel-library async-await

在ac #project项目中,我正在调用一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是想知道这是否是正确的路径,或者我应该尝试并行整个DeployView(即使在使用HttpClient之前)

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

谢谢

sha*_*y__ 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
    }
Run Code Online (Sandbox Code Playgroud)

但是,您可以在处理响应时使用并行性.您可以使用以下"foreach"循环代替上述"foreach"循环:

Parallel.Foreach(tasks.Select(p=> p.Result), response => ProcessResponse(response));
Run Code Online (Sandbox Code Playgroud)

但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);   
Run Code Online (Sandbox Code Playgroud)

第一个和最后一个示例之间存在重大差异:在第一个示例中,您有一个线程启动异步请求,等待(非阻塞)所有它们返回,然后才处理它们.在第二个示例中,您将连接附加到每个任务.这样,每个响应一到达就会得到处理.假设当前的TaskScheduler允许并行(多线程)执行任务,则没有响应像第一个示例那样保持空闲状态.

*编辑-如果你决定这样做是平行的,你可以只使用一个HttpClient的实例-这是线程安全的.


Yuv*_*kov 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

HttpClient能够进行并发请求(请参阅@usr链接了解更多信息),因此我没有看到每次在lambda中创建新实例的原因.请注意,如果您DeployViewAsync多次使用,也许您希望保持HttpClient身边而不是每次分配一个,并在您不再需要其服务时将其丢弃.