Task.WhenAll 为什么偶尔会花费很长时间?

3
我有一个运行在ASP.NET上的网站,处理了一定数量的请求(大约分布在3个服务器上的每分钟500个请求),通常请求需要15毫秒左右。然而,我发现经常会出现耗时很长的请求(1秒或更长时间)。我已经确定延迟是由于调用Task.WhenAll引起的。以下是有问题的代码示例:
var taskA = dbA.GetA(id);
var taskB = dbB.GetB(id);
var taskC = dbC.GetC(id);
var taskD = dbD.GetD(id);

await Task.WhenAll(taskA, taskB, taskC, taskD);

每个单独的任务都有度量,并且完成时间不超过10毫秒。我已经将延迟定位到了Task.WhenAll调用,似乎与任务的调度方式有关。据我所知,TPL任务池没有太大的压力,因此我不知道为什么性能如此不稳定。请注意保留HTML标签。

我会尝试使用嗅探器,看看互联网上的时间间隔是否与应用程序中的间隙相关。 - jdweng
我已经确定问题出在对TPL的调用上。单个网络请求的测量时间从未超过10毫秒。这是在进程中发生的。 - Eric Andres
1
@EricAndres 抱歉,这个问题有点离题,但出于我的好奇心:你用了什么工具得出这样的结论? - CodingYoshi
你有测量过 Task.WhenAll 还是 await Task.WhenAll 吗?因为它们会测量非常不同的东西。 - Kevin Gosse
那是我考虑过的一个选项,但在开发环境中很难复现这个问题。 - Eric Andres
显示剩余7条评论
2个回答

0
异步操作涉及上下文切换,这需要花费时间。不幸的是,并非总是以确定性的方式进行。为了加快速度,请尝试在您的情况下,在Task.WhenAll调用之前加上ConfigureAwait(false),如下所示:
await Task.WhenAll(taskA, taskB, taskC, taskD).ConfigureAwait(false);

这将消除额外的上下文切换,这实际上是服务器端应用程序推荐的方法。

2
忽略 ConfigureAwait 对于上下文切换不会产生任何改变的事实,我们正在谈论的是整整一秒的延迟,而不仅仅是几微秒。 - Kevin Gosse
这是一个我想要尝试的实验:
  1. 在调用Task.WhenAll之前,也启动另一个任务(它只会在输出中打印一些内容),然后再调用Task.WhenAll。有趣的是看看新任务是否具有相同的延迟,或者它会再次非常快地完成,从而确认问题确实与Task.WhenAll相关。
- Artak

-2
创建线程会带来一些开销。根据你的需求,你也可以尝试使用 Parallel.ForEach。
public static void yourMethod(int id){

    var tasks = new List<IMyCustomType> { new dbA.GetA(id), new dbB.GetB(id), new dbC.GetC(id), new dbD.GetD(id)};

    // Your simple stopwatch for timing
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    // For each 'tasks' list item, call 'executeTasks' (Max 10 occurrences)
    // - Processing for all tasks will be complete before 
    //   continuing processing on the main thread
    Parallel.ForEach(tasks, new ParallelOptions { MaxDegreeOfParallelism = 10 }, executeTasks);

    stopWatch.Stop();
    Console.WriteLine("Completed execution in:  " + stopWatch.Elapsed.TotalSeconds);
}

private static void executeTasks(string obj)
{
    // Your task's work here. 
}

你怎么知道OP在使用多个线程?你只知道他们有异步操作,并且正在进行数据库调用(这不需要线程,所以如果OP在使用它们,那就没有理由这样做)。你的代码增加了大量的线程工作。而且这段代码根本无法编译,因为OP的方法都返回任务,并且不像你处理的那样是同步的。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接