我需要从抵押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调用的正确方法提供建议?
答案 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;
}