如何在 C# 中为大型 HTTP 请求设置 HttpWebRequest.Timeout



我不知道如何处理HttpWebRequest.Timeout。以前,我曾经为 Socket 对象设置超时,这是直截了当的:超时设置发送或接收数据块的最长时间。但是,似乎HttpWebRequest.Timeout设置了整个HTTP请求的超时。如果请求很大(例如,我正在使用HTTP PUT上传一个大文件(,则可能需要数小时。这导致我设置:

...
request.Timeout = System.Threading.Timeout.Infinite;
Stream requestStream = request.GetRequestStream();
for (something)
{
  ...
  requestStream.Write(b, 0, b.Length);
}

但是,这是否意味着如果与服务器的网络连接卡住,我最终会得到requestStream.Write永远不会抛出"操作超时"异常?所以在这种情况下超时的概念就行不通了?

理想情况下,我想要那个.超时设置仅影响单个请求流。我可以根据需要拥有任意数量的 Write((,前提是每个 Write(( 永远不会超过 .超时值。

还是我需要实现自己的基于 Socket 的机制来实现这一点?

另外,在设置断点时,我发现requestStream实际上是具有.超时 = 300000(300 秒(。这是否意味着无限实际上不是无限的,并且仅限于 300 秒?对于大文件和慢速连接,这是一个相当严格的限制。

处理大文件上传时,有两个超时困扰着我们。 HttpWebRequest.TimeoutHttpWebRequest.ReadWriteTimeout .我们需要解决这两个问题。

HttpWebRequest.ReadWriteTimeout

首先,让我们解决HttpWebRequest.ReadWriteTimeout。我们需要禁用"写入流缓冲"。

httpRequest.AllowWriteStreamBuffering = false;

更改此设置后,您的HttpWebRequest.ReadWriteTimeout值将神奇地按照您希望的方式工作,您可以将它们保留为默认值(5 分钟(甚至减少它们。我用60秒。

出现此问题的原因是,在上传大文件时,如果数据由 .NET Framework 缓冲,则代码会认为上传已完成,而实际上并非如此,并且过早调用HttpWebRequest.GetResponse(),这将超时。

HttpWebRequest.Timeout

现在,让我们解决HttpWebRequest.Timeout

我们的第二个问题出现了,因为HttpWebRequest.Timeout适用于整个上传。上传过程实际上包括三个步骤(这是我的主要参考资料的一篇很棒的文章(:

  1. 开始上载的请求。
  2. 写入字节。
  3. 完成上载并从服务器获取任何响应的请求。

如果我们有一个适用于整个上传的超时,我们需要一个很大的数字来容纳上传大文件,但我们也面临着一个问题,即合法的超时需要很长时间才能真正超时。这不是一个好情况。相反,我们希望有很短的时间(例如 30 秒(应用于步骤 #1 和 #3。我们根本不希望 #2 出现总体超时,但我们确实希望如果字节停止写入一段时间,上传会失败。值得庆幸的是,我们已经用HttpWebRequest.ReadWriteTimeout解决了#2,我们只需要修复HttpWebRequest.Timeout的烦人行为。事实证明,GetRequestStreamGetResponse的异步版本完全符合我们的需要。

所以你需要这段代码:

public static class AsyncExtensions
{
    public static Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
    {
        return Task.Factory.StartNew(() =>
        {
            var b = task.Wait((int)timeout.TotalMilliseconds);
            if (b) return task.Result;
            throw new WebException("The operation has timed out", WebExceptionStatus.Timeout);
        });
    }
}

我们不会调用GetRequestStreamGetResponse而是调用异步版本:

var uploadStream = httpRequest.GetRequestStreamAsync().WithTimeout(TimeSpan.FromSeconds(30)).Result;

同样,对于响应:

var response = (HttpWebResponse)httpRequest.GetResponseAsync().WithTimeout(TimeSpan.FromSeconds(30)).Result;

这就是您所需要的一切。现在,您的上传将更加可靠。您可以将整个上传包装在重试循环中以增加确定性。

请注意,对于带有单声道的 HttpWebRequest,使用这一点非常重要(如上所述 Boinst

httpRequest.AllowWriteStreamBuffering = false;

我花了一整天的时间与Wireshark一起追查为什么大文件上传不成功,最后偶然发现了这个快速,简单的答案。

最简单的方法是创建自己的类,它继承了WebRequest

    public class MyRequest : WebRequest
    {
        public MyRequest()
        {
            this.Timeout = 1234;
        }
    }

最新更新