【问题标题】:C# Concurrent multiple in Parallel HttpClient calls with timeoutC# 并发多个并行 HttpClient 调用超时
【发布时间】:2017-02-20 20:06:32
【问题描述】:

我有一个 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(八)台 D3_V2 Azure 4 核机器,从而导致疯狂的开支,并且超时时间越长,CPU 使用率就越高。

有没有更好的方法在不使用这么多 CPU 资源的情况下做到这一点(也许 Parallel Linq 是比这更好的选择)?

如果端点没有及时回复,HttpClient 超时是否足以停止调用并返回,而不必使用WhenAny 中的第二个任务?

更新:

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

【问题讨论】:

  • 您能否提供有关您正在调用的端点的类型和性质的更多信息,缓存部分结果在您的用例中是否可行,为什么会出现硬超时(这似乎是成本驱动,但还有其他原因),您是否可以访问端点的代码和基础设施,或者您是否正在调用第 3 方 api?
  • 我在问题中添加了一些信息。我想知道现在的实现方式是否有问题,以及由于 .net 具有多种并行/并发解决方案,是否有可能做得更好

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


【解决方案1】:

您使用这两个任务只是为了超时的方法确实有效,但您可以做更好的事情: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();

【讨论】:

  • 我尝试了cancellationtoken 方法,但取消令牌并不总是受到Httpclient 的尊重。我做了一些研究,最终几乎一直遇到这个问题:stackoverflow.com/questions/29319086/… httpclient 创建一个新的cancellationToken 并尝试完成如果需要10 秒的操作,则在这段时间内阻止所有API。这就是我选择严格的两个任务解决方案的原因,但它的计算量太大。
  • 我将调查并尝试 PLINQ 方法并发布更新,感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-13
  • 1970-01-01
  • 1970-01-01
  • 2022-09-23
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
相关资源
最近更新 更多