并行HttpClient中的C#并发多次调用超时

时间:2017-02-20 20:06:33

标签: c# concurrency task-parallel-library asynchttpclient

我的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中的第二个任务?

更新

  • 端点是第三方API,我不知道代码或有任何控制,调用以JSON完成并返回JSON或字符串。
  • 他们中的一些人偶尔会在10秒以上回复或者卡住并且非常慢,所以超时是释放线程并返回,即使其他部分数据及时返回也是如此。
  • 缓存是可能的,但只是部分,因为数据一直在变化,如股票和外汇实时货币交易。

1 个答案:

答案 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();