我的API必须并行调用 4个HttpClients ,支持每秒 500个用户的并发性(所有这些用户同时调用API)
必须有严格的超时,即使并非所有HttpClients调用都返回了值,API也会返回结果。
端点是外部第三方API,我对它们没有任何控制权或知道代码
我对此事进行了广泛的研究,但即使有很多解决方案可行,我也需要尽可能少消耗CPU的那个,因为我的服务器预算很低。
到目前为止,我提出了这个问题:
var conn0 = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(1000),
BaseAddress = new Uri("http://endpoint")
};
var conn1 = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(1000),
BaseAddress = new Uri("http://endpoint")
};
var conn2 = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(1000),
BaseAddress = new Uri("http://endpoint")
};
var conn3 = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(1000),
BaseAddress = new Uri("http://endpoint")
};
var list = new List<HttpClient>() { conn0, conn1, conn2, conn3 };
var timeout = TimeSpan.FromMilliseconds(1000);
var allTasks = new List<Task<Task>>();
//the async DoCall method just call the HttpClient endpoint and return a MyResponse object
foreach (var call in list)
{
allTasks.Add(Task.WhenAny(DoCall(call), Task.Delay(timeout)));
}
var completedTasks = await Task.WhenAll(allTasks);
var allResults = completedTasks.OfType<Task<MyResponse>>().Select(task => task.Result).ToList();
return allResults;
我使用WhenAny和两个任务,一个用于呼叫,一个用于超时。如果呼叫任务迟到,则另一个任务返回。
现在,这段代码完美无缺,一切都是异步的,但我想知道是否有更好的方法来实现这一目标 曾经单独调用此API会产生大量线程,而对于500个并发用户,它需要大量的8(8)个D3_V2 Azure 4核机器,导致费用疯狂,超时越高,CPU使用率就越高。
有没有更好的方法可以在不使用这么多CPU资源的情况下做到这一点(也许Parallel Linq比这更好的选择)?
HttpClient超时是否足以停止呼叫并在端点未及时回复时返回,而不必使用WhenAny
中的第二个任务?
更新
答案 0 :(得分:2)
使用这两个任务只是为了超时的方法确实有效,但你可以做得更好:use CancellationToken
for the task,以及从服务器获取答案:
var cts = new CancellationTokenSource();
// set the timeout equal to the 1 second
cts.CancelAfter(1000);
// provide the token for your request
var response = await client.GetAsync(url, cts.Token);
之后,您只需过滤completed任务:
var allResults = completedTasks
.Where(t => t.IsCompleted)
.Select(task => task.Result).ToList();
此方法将减少您创建的任务数量不少于两次,并将减少服务器上的开销。此外,它将为您提供一种简单的方法来取消部分处理甚至整个处理。如果您的任务完全相互独立,您可以使用Parallel.For
来呼叫http客户端,但仍然使用token for cancelling the operation:
ParallelLoopResult result = Parallel.For(list, call => DoCall(call, cts.Token));
// handle the result of the parallel tasks
或使用PLINQ:
var results = list
.AsParallel()
.Select(call => DoCall(call, cts.Token))
.ToList();