如何在C#中进行并发API调用?

时间:2019-09-27 15:11:28

标签: c# api httpclient

我需要从抵押API中提取自定义字段。 问题是总共有11000条记录,每个API请求花费1秒。我想找到一种异步和并行发送请求的方法,以提高效率。

我尝试遍历所有请求,然后让Task.WaitAll()等待响应返回。我只收到两个响应,然后应用程序无限期等待。

我首先为HttpClient

设置了一个静态类
 public static class ApiHelper
    {
        public static HttpClient ApiClient { get; set; }

        public static void InitializeClient()
        {
            ApiClient = new HttpClient();
            ApiClient.DefaultRequestHeaders.Add("ContentType", "application/json");
        }
    }

我收集抵押物ID列表并遍历API调用

        static public DataTable GetCustomFields(DataTable dt, List<string> cf, string auth)
        {

                //set auth header
                ApiHelper.ApiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", auth);

                //format body
                string jsonBody = JArray.FromObject(cf).ToString();
                var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");



                var responses = new List<Task<string>>();


                foreach (DataRow dr in dt.Rows)
                {

                    string guid = dr["GUID"].ToString().Replace("{", "").Replace("}", ""); //remove {} from string

                    responses.Add(GetData(guid, content));

                }

                Task.WaitAll(responses.ToArray());
                //some code here to process through the responses and return a datatable

                return updatedDT;

        }

每个API调用都需要URL中的抵押ID(GUID)

  async static Task<string> GetData(string guid, StringContent json)
        {

            string url = "https://api.elliemae.com/encompass/v1/loans/" + guid + "/fieldReader";
            Console.WriteLine("{0} has started .....", guid);
            using (HttpResponseMessage response = await ApiHelper.ApiClient.PostAsync(url, json))
            {
                if (response.IsSuccessStatusCode)
                {
                    Console.WriteLine("{0} has returned response....", guid);
                    return await response.Content.ReadAsStringAsync();
                }
                else
                {
                    Console.WriteLine(response.ReasonPhrase);
                    throw new Exception(response.ReasonPhrase);
                }

            }

        }

我现在仅测试10条记录,并发送所有10条请求。 但是我只收到两个回来。

结果为here

能否请您就发送并发API调用的正确方法提供建议?

1 个答案:

答案 0 :(得分:1)

所有GetData Task都使用相同的HttpClient单例实例。 HttpClient不能同时处理多个调用。最佳做法是使用HttpClient的Pool,以确保没有Task同时访问同一HttpClient。

另外,请小心在Task中抛出exception,它将在第一次抛出异常时停止WaitAll()

解决方案:我已经将整个项目发布到了这里:https://github.com/jonathanlarouche/stackoverflow_58137212
该解决方案使用max sized [3]池发送25个请求;

基本上,ApiHelper包含一个HttpClient ,使用通用类 ArrayPool<T> 您可以使用任何其他Pooling库,我只想发布一个独立的解决方案

建议使用ApiHelper 贝娄,该类现在包含一个池和一个Use的{​​{1}}方法,该池中的项目将在此期间被“租用”动作,然后将其通过Action函数返回到池中。 ArrayPool.Use函数还会接收apiToken来更改请求身份验证标头。

Use

GetData 函数。获取数据将接收apiToken并等待public static class ApiHelper { public static int PoolSize { get => apiClientPool.Size; } private static ArrayPool<HttpClient> apiClientPool = new ArrayPool<HttpClient>(() => { var apiClient = new HttpClient(); apiClient.DefaultRequestHeaders.Add("ContentType", "application/json"); return apiClient; }); public static Task Use(string apiToken, Func<HttpClient, Task> action) { return apiClientPool.Use(client => { client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiToken); return action(client); }); } } 函数。 ApiHelper.Use对象的新实例需要在此函数中完成,因为它不能在不同的Http Post调用中重复使用。

StringContent()

ArrayPool

async static Task<string> GetData(string apiToken, Guid guid, string jsonBody)
{

    string url = "https://api.elliemae.com/encompass/v1/loans/" + guid + "/fieldReader";
    Console.WriteLine("{0} has started .....", guid);
    string output = null;
    await ApiHelper.Use(apiToken, (client) => 
    {
        var json = new StringContent(jsonBody, Encoding.UTF8, "application/json");
        return client.PostAsync(url, json).ContinueWith(postTaskResult =>
        {

            return postTaskResult.Result.Content.ReadAsStringAsync().ContinueWith(s => {

                output = s.Result;
                return s;
            });
        });
    });
    Console.WriteLine("{0} has finished .....", guid);
    return output;
}