DownloadFileAsync引发Get TimeOut异常

时间:2018-12-28 21:14:50

标签: c# flurl

我已使用DownloadFileAsync方法将Flurl置于高负载状态,以将专用网络中的文件从一台服务器下载到另一台服务器,几个小时后,该方法开始引发异常“ Get TimeOut”。解决此问题的唯一方法是重新启动应用程序。

downloadUrl.DownloadFileAsync(Helper.CreateTempFolder()).Result;

我使用HTTPClient添加了第二种方法作为故障转移,并且在flurl失败后其下载文件正常,因此这不是服务器问题。

private void DownloadFile(string fileUri, string locationToStoreTo)
       {
           using (var client = new HttpClient())
           using (var response = client.GetAsync(new Uri(fileUri)).Result)
           {
               response.EnsureSuccessStatusCode();

               var stream = response.Content.ReadAsStreamAsync().Result;

               using (var fileStream = File.Create(locationToStoreTo))
               {
                   stream.CopyTo(fileStream);
               }
           }
       }

您知道为什么使用该方法在出现高负载时会出现“弹出超时”错误吗?

public static Task<string> DownloadFileAsync(this string url, string localFolderPath, string localFileName = null, int bufferSize = 4096, CancellationToken cancellationToken = default(CancellationToken));

两个下载代码的不同之处仅在于 Flurl 对所有请求重复使用HttpClient实例,而我的代码销毁并为每个新请求创建新的HttpClient对象。我知道创建和销毁HttpClient会浪费时间和资源,如果可以的话,我宁愿使用Flurl。

2 个答案:

答案 0 :(得分:0)

我可以想到两种或三种可能性(我敢肯定还有其他我想不到的可能性)

  1. 服务器IP地址已更改。

您写道Flurl重用了HttpClient。我从未使用过Flul,甚至从未听说过它,所以我不知道它是如何工作的。但是HttpClient重用了一个连接池,这就是为什么重用单个实例的效率很高,并且为什么在大容量微服务应用程序中重用它至关重要,否则您可能会耗尽所有端口,但是不同的错误消息,不是超时,所以我知道您还没有遇到这种情况。但是,尽管短期内重用HttpClient很重要,但HttpClient会缓存DNS结果,这意味着定期处理和创建新的HttpClient很重要。在短暂的进程中,可以使用静态或单例实例。但是在长时间运行的流程中,您应该定期创建一个新实例。如果仅使用它来访问一台服务器,则该服务器的DNS TTL是一个很好的使用值。

因此,可能发生的情况是服务器在您的程序启动后几个小时更改了IP地址,并且由于Flurl继续重复使用相同的HttpClient,因此它无法从DNS条目中获取新的IP地址。一种检查是否是问题的方法是在过程开始时将服务器的IP地址写到日志中,遇到问题时,请检查IP地址是否相同。

如果这是问题所在,则可以研究ASP.NET Core 2.1的HttpClientFactory。在ASP.NET之外使用它有点尴尬,但我只做了一次。它使您可以重新使用HttpClient,从而避免了在120秒内使用超过32k HttpClient的TCP端口耗尽的问题,而且还避免了DNS缓存问题。我的记忆是,默认情况下,它每5分钟创建一个新的HttpClient。

  1. 达到每个服务器的最大连接数

ServicepointManager.DefaultConnectionLimit设置客户端将打开的HTTP连接的最大数量。如果您的代码尝试同时使用更多代码,则超出限制的请求将等待现有HTTP客户端完成其请求,然后它将使用新可用的连接。但是,在过去,当我查看此内容时,HTTP超时从调用HttpClient的方法时开始,而不是从HttpClient通过连接向服务器发送请求时开始。这意味着,如果您的限制为2,并且两者的使用时间都超过了超时期限(例如,如果下载2个大文件),则即使没有HTTP请求发送到服务器,其他从同一服务器下载的请求也会超时。服务器。

因此,根据您的应用程序和服务器,您也许可以使用更高的连接限制,否则您需要在应用程序中实现请求排队。

  1. 线程池耗尽

当在高度并发,受IO约束的工作负载中正确使用异步代码时,其性能非常出色。有时,我认为在其他任何地方使用它都是一个坏主意,因为如果使用不当,它有巨大的潜力导致奇怪的问题。就像Crowcoder在对该问题的评论中所写的那样,在异步上下文中,您不应使用.Result或任何阻塞正在运行的线程的代码。尽管您提供的代码示例是public void DownloadFile(...,但实际上是public async Task DownloadFile(...还是从异步方法中调用了DownloadFile,则确实存在问题的风险。如果不是通过异步方法调用DownloadFile,而是在线程池上调用DownloadFile,则存在相同的错误风险。

了解异步是一个巨大的话题,不幸的是,互联网上也存在很多错误信息,因此在这里我无法对其进行详细介绍。要注意的关键是异步任务在线程池上运行。因此,如果您调用ThreadPool.QueueUserWorkItem并阻止代码在其上运行的线程,或者如果您有要阻塞的异步任务(例如,通过调用.Result),则可能发生的情况是您阻塞了代码中的每个线程。线程池,并且当HTTP响应从网络返回时,.NET运行时没有可用的线程来完成任务。这个想法的问题在于,也没有可用的线程来发出超时信号,所以我不认为您正在耗尽线程池(如果您曾用过,那我会期望出现死锁),但是我不知道如何超时已实现。如果超时/计时器使用专用线程,则可以由计时器的线程设置取消令牌(表示超时的事物),然后阻塞中的任何代码都可以等待HTTP响应或取消令牌被触发。但是线程池耗尽通常会导致死锁,因此如果您返回错误,可能不是这样。

要检查线程池耗尽问题,请在程序开始出现超时错误时获取应用程序的内存转储(例如,使用任务管理器)。如果您具有Visual Studio的Enterprise或Ultimate SKU,则可以在VS中打开/​​调试内存转储。否则,您将需要学习如何使用windbg(或找到其他工具)。调试内存转储时,请检查线程数。如果线程数量很多,则暗示您可能在正确的轨道上。检查内存转储时线程在哪里。如果它们全都阻塞了诸如WaitForObject之类的调用,或者类似的调用,那么确实存在耗尽线程池的风险。我之前从未调试过异步任务死锁/线程池耗尽问题,所以我不确定是否有办法获取任务列表并从其运行状态查看它们是否可能被死锁。如果在运行状态下看到的任务多于CPU上的核心,那么几乎可以肯定,异步任务中存在阻塞。

总而言之,您没有提供足够的详细信息,无法给您以100%确定性的答案。您需要继续调查以了解问题,直到您可以自己解决问题或向我们提供更多信息为止。我已经给了您一些最可能的原因,但是很可能完全是其他原因。

答案 1 :(得分:0)

正如其他人指出的那样,您正在尝试通过调用<a href="https://www.google.com" target="blank">Google</a> 来同步使用Flurl。不支持此功能,在高度并发的工作负载下,您最有可能遇到死锁。

.Result解决方案为每个调用都使用一个新实例,并且由于根本不共享实例,因此不太可能出现死锁。但这引出了一个全新的问题:port exhaustion

简而言之,如果您想继续使用Flurl,请继续这样做,尤其是因为您变得聪明HttpClient可以“免费”重用。只需按预期异步使用它(HttpClient / async)即可。有关更多信息和示例,请参见docs