【问题标题】:Timeout Exception - Queuing of Requests? Not enough threads?超时异常 - 请求排队?线程不够?
【发布时间】:2014-06-04 23:08:18
【问题描述】:

背景:

我有一项服务可以汇总来自多个其他服务的数据。为了让事情及时发生,我在整个代码中使用异步,然后将各种请求收集到一个任务列表中。

以下是部分代码摘录:

private async Task<List<Foo>> Baz(..., int timeout)
{
    var tasks = new List<Task<IEnumerable<Foo>>>();
    Tasks.Add(GetFoo1(..., timeout));
    Tasks.Add(GetFoo2(..., timeout));
    // Up to 6, depending on other parameters.  Some tasks return multiple objects.

    return await Task.WhenAll(tasks).ContinueWith((antecedent) => { return antecedent.Result.AsEnumerable().SelectMany(f => f).ToList(); }).ConfigureAwait(false);
}    
private async Task<IEnumerable<Foo>> GetFoo1(..., int timeout)
{
Stopwatch sw = new Stopwatch();
sw.Start();

    var value = await SomeAsyncronousService.GetAsync(..., timeout).ConfigureAwait(false);

sw.Stop();
// Record timing...
    return new[] { new Foo(..., value) };
}
private async Task<IEnumerable<Foo>> GetFoo2(..., int timeout)
{
return await Task.Run(() => {
    Stopwatch sw = new Stopwatch();
    sw.Start();
    var r = new[] { new Foo(..., SomeSyncronousService.Get(..., timeout)) };
    sw.Start();
    sw.Stop();
    // Record timing...
    return r;
}).ConfigureAwait(false);
}  

// In class SomeAsyncronousService
public async Task<string> GetAsync(..., int timeout)
{
...
    try
    {
        using (var httpClient = HttpClientFactory.Create())
        {
            // I have tried it with both timeout and CTS.  The behavior is the same.
            //httpClient.Timeout = TimeSpan.FromMilliseconds(timeout);
            var cts = new CancellationTokenSource();
            cts.CancelAfter(timeout);

            var content = ...;
            var responseMessage = await httpClient.PostAsync(Endpoint, content, cts.Token).ConfigureAwait(false);
            if (responseMessage.IsSuccessStatusCode)
            {
                var contentData = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
                ...
                return ...
            }
            ...             
        }
    }
    catch (OperationCanceledException ex)
    {
        // Log statement ...
    }
    catch (Exception ex)
    {
        // Log statement ...
    }
    return ...;
}

症状:

这段代码在我的本地机器上运行良好,并且大部分时间在我们的测试服务器上运行良好。但是,偶尔我们会收到大量记录的超时 - 由上面的“记录时间”cmets 和 OperationCanceledExceptions 上的 Log 语句记录。我无法判断我调用的服务是否真的超时了。

现在,当我说一系列超时时,我的意思是大多数或所有任务(以及除一个之外都使用的 HttpClients,另一个使用 WCF 服务)几乎同时超时。

现在,我知道你在想什么,我正在通过相同的超时。没错,但我在 250 毫秒内通过,各种秒表报告的运行时间约为 800 毫秒或更高。

现在,我确实在日志中看到了 OperationCanceledExceptions,但异常的时间戳与秒表结束时(或在 2-3 毫秒内)的时间戳相同,并且我的服务失败了,因为客户期待它会在 500 毫秒或更短的时间内做出响应,而不是 800 毫秒。

现在,各种服务通常会在 100 毫秒内做出响应,结果之间存在很大差异。当我们出现问题时,大多数/全部在 800 毫秒或更长时间内返回,它们仅相差约 10 毫秒。我调用的依赖项都在不同的域上。似乎不太可能所有这些人都真的需要这么长时间才能同时做出回应。

我想可能存在网络问题,同时影响所有请求,但我们网络中的其他服务不会遇到相同的行为 - 它仅限于我正在编写的新服务。

即使是这种情况,我也希望取消异常会在 250 毫秒后发生,然后任务结束并且秒表记录 250(加上 5-20 毫秒左右的异常处理时间)。

所以我认为这不是网络问题。现在我确信至少部分问题与我没有正确取消/超时有关,但在我看来,来自服务的所有发出的请求都同时受到影响,与 HttpClient 无关。

我之所以这么说是因为当其余请求超时时,WCF 服务也会显示 800+ 毫秒(根据秒表)。 WCF 服务不是异步的。超时设置如下:

var binding = new BasicHttpBinding()
{
    Security = new BasicHttpSecurity()
    {
        Mode = BasicHttpSecurityMode.TransportCredentialOnly,
        Transport = new HttpTransportSecurity()
        {
            ClientCredentialType = HttpClientCredentialType.Ntlm
        }

    },
    ReceiveTimeout = TimeSpan.FromMilliseconds(timeout)
};

问题:

因此,简而言之,我认为某些原因导致对任何域的所有传出请求都暂停或排队,从而导致观察到的行为。

我花了几天时间试图弄清楚发生了什么,但没有运气。有什么想法吗?

编辑

我认为正在发生的事情是请求被搁置,因为没有可用的线程,然后几百毫秒后线程可用并且任务开始。计时方法调用显示它需要 800 毫秒,但 HttpClient 上的超时不会开始,直到有线程可用于运行异步调用。

这也可以解释为什么我看到该方法需要 800+ 毫秒,但有时它仍然完成而没有显示超时异常。其他时候它会抛出一个超时异常并且没有完成。

我尝试在 Application_Start 中将 ServicePointManager.DefaultConnectionLimit 设置为 200,但这并没有解决问题。

与我们的其他服务相比,该服务没有占用那么多流量,而且其他服务似乎都没有同样的问题。

有什么想法吗?

编辑 2

我在进行(次要)负载测试时登录了该框并监控了 netstat。

使用 HttpClient,每秒有 1-2 个请求,端口将显示 ESTABLISHED,然后移动到 TIME_WAIT 大约 4 分钟。每秒有 3 个以上的请求,我最终会得到大约每秒 100 个恒定请求的 ESTABLISHED 端口(因此每秒 3 个负载测试为 300 个),然后我会开始看到它们转到 CLOSE_WAIT 而不是 TIME_WAIT - 表示错误关闭条件。同时,我会看到异常数量和执行请求的时间激增。 (TcpTimedWaitDelay 不适用于 CLOSE_WAIT)。

所以我重写了整个事情以串行使用 HttpWebRequests,而不是并行使用 HttpClient。然后我进行了同样的测试。

现在 ESTABLISHED 端口等于每秒 0-2 个请求,然后端口按预期移动到 TIME_CLOSE。性能和吞吐量有所提高,但并未完全清除。

然后我将 TcpTimedWaitDelay 设置为 30(默认为 24​​0)。性能显着提高。我有一个原始负载测试,每秒 40 个请求,没有任何问题。我会得到一个更彻底的测试设置,但我认为问题已经解决了。

我不知道发生了什么,但 HttpClient 似乎没有正确关闭下面的临时端口。我公司的许多开发人员和架构师都看过它,并且看不出代码有什么问题。我尝试在每个请求的 using 语句中使用一个 HttpClient,以及在后端调用的每个 api 使用一个 HttpClient。我尝试过并行和串行使用 HttpClient。我已经尝试过使用 async/await 和没有。无论我尝试什么行为都是一样的。

我希望能够使用 HttpClient,但我不能再花时间在这个问题上,因为我已经使用 HttpWebRequest。我的下一步是让 HttpWebRequests 并行发生。

感谢您的意见。

【问题讨论】:

  • 您可能需要检查您的 WCF 设置以查看您的服务是否设置为允许多个并发请求。例如,如果您打开了节流,然后其中一个请求阻塞了很长时间,那么在它之后进来的所有请求都会等到它完成后才会被处理。
  • 我删除了调用WCF服务的任务,行为是一样的。还有其他想法吗?
  • 不要将awaitContinueWith 混用。只需使用等待。我会让你的代码更容易理解。
  • 您的应用程序是什么类型的?客户端(WCF、WinForms、SL 等)还是服务器(ASP.NET、Windows 服务等)?
  • 这是一个 WebApi 服务应用程序。

标签: c# task-parallel-library async-await dotnet-httpclient


【解决方案1】:

我在使用 HttpClient 时也遇到过类似的挫败感。在我的场景中,我发现在 ServicePointManager 上将 MaxServicePointIdleTime 设置为低得多的值并将 DefaultConnectionLimit 设置为高值解决了我的问题。我相信在我的情况下,当连接保持打开状态时,我正在经历池饥饿。

您可能还想在没有附加调试器的情况下在发布时进行测试,如果您还没有这样做的话,因为 TaskScheduler 在调试时的行为会有所不同。

下面的 MSDN 文章很有帮助:http://blogs.msdn.com/b/jpsanders/archive/2009/05/20/understanding-maxservicepointidletime-and-defaultconnectionlimit.aspx

【讨论】:

    猜你喜欢
    • 2014-02-12
    • 2014-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多