C#:The operation has timed out

The operation has timed out 异常,主要原因是 ‌HttpWebRequest 的超时机制配置不当‌ 以及 ‌资源管理存在隐患‌。

虽然设置了 req.Timeout = 1500(1.5秒),但这对于网络请求来说非常短,且 Timeout属性并不覆盖所有阶段。以下是具体原因分析和修复方案:

  1. 核心原因分析

A. Timeout 属性覆盖范围有限

HttpWebRequest.Timeout 仅控制 GetResponse() 和 GetRequestStream() 的同步等待时间。它‌不包含‌ DNS 解析时间。

DNS 解析延迟‌:如果 URL 需要 DNS 解析,Windows 系统默认的 DNS 超时可能长达 15 秒。如果 DNS 解析超过 1.5 秒,即使 Timeout 设为 1500,请求也可能在 DNS 阶段卡住直到系统级超时,或者在连接建立初期就报错。

读写超时未设置‌:Timeout 不控制数据读取过程中的空闲时间。如果服务器接收了请求但发送响应很慢,或者网络波动导致数据包间隔长,可能会触发底层的 Socket 超时,而非 HttpWebRequest 的 Timeout。应配合使用 ReadWriteTimeout。

B. 超时时间过短 (1500ms)

1.5 秒对于包含 DNS 解析、TCP 握手、TLS 协商(如果是 HTTPS)以及服务器处理时间的完整 HTTP 请求来说,通常太短。任何轻微的网络抖动或服务器负载稍高都会导致超时。

C. Expect100Continue 导致的额外往返

默认情况下,HttpWebRequest 会发送 Expect: 100-continue 头。这意味着客户端先发送头部,等待服务器返回 100 Continue 状态码后,再发送 Body 数据。

如果服务器不支持或忽略此头,客户端会等待一段时间(通常 350ms - 1s)后才发送 Body。

在 1500ms 的极短超时限制下,这额外的等待极易导致总耗时超标。

D. 连接池耗尽 (Connection Limit)

HttpWebRequest 默认对同一域名的并发连接数限制为 ‌2‌ (DefaultConnectionLimit)。

如果之前的请求没有正确关闭 Response 或 Stream,连接会保持在 "Alive" 状态。

当并发请求超过 2 个时,新请求会在队列中等待可用连接。这个‌等待连接的时间也计入 Timeout‌。如果前两个请求处理慢,第三个请求可能在拿到连接之前就超时了。

E. 资源未正确释放

代码中 resp 和 stream 没有在 finally 块或 using 语句中确保关闭。如果发生异常,连接可能泄露,进一步加剧连接池耗尽问题。

  1. 代码修复

using System;

using System.IO;

using System.Net;

using System.Text;

public string DoHttpPost(string url, string postData, string sKey)

{

string result = "";

HttpWebRequest req = null;

HttpWebResponse resp = null;

DateTime stciDT = System.DateTime.Now;

try

{

req = (HttpWebRequest)WebRequest.Create(url);

req.Method = "POST";

// 1. 增加超时时间至合理范围 (例如 5-10 秒),1500ms 太短

req.Timeout = 5000;

// 2. 设置读写超时,防止数据传输过程中卡顿

req.ReadWriteTimeout = 5000;

// 3. 禁用 Expect100Continue,减少一次往返等待,加速请求

req.ServicePoint.Expect100Continue = false;

// 4. 保持连接复用,但确保后续正确关闭

req.KeepAlive = true;

req.ContentType = "application/json";

req.Headers.Add("key", sKey);

byte\[\] data = Encoding.UTF8.GetBytes(postData);

req.ContentLength = data.Length;

// 使用 using 确保请求流正确关闭

using (Stream reqStream = req.GetRequestStream())

{

reqStream.Write(data, 0, data.Length);

} // using 结束会自动 Close 和 Dispose

// 获取响应

resp = (HttpWebResponse)req.GetResponse();

// 使用 using 确保响应流和 StreamReader 正确关闭

using (Stream stream = resp.GetResponseStream())

using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))

{

result = reader.ReadToEnd();

}

}

catch (WebException we)

{

// 专门捕获 WebException 以区分超时和其他网络错误

if (we.Status == WebExceptionStatus.Timeout)

{

LogManager.GetLogger().ErrorEx("DoHmCdssHttpPost 超时: " + url);

}

else

{

LogManager.GetLogger().ErrorEx("DoHmCdssHttpPost 网络错误: " + we.Message);

}

}

catch (Exception e)

{

LogManager.GetLogger().ErrorEx("DoHmCdssHttpPost 未知异常: " + e.Message);

}

finally

{

// 5. 确保在 finally 中关闭响应对象,释放连接回连接池

if (resp != null)

{

resp.Close();

}

// 记录耗时

DateTime stciafterDT = System.DateTime.Now;

TimeSpan stciadt = stciafterDT.Subtract(stciDT);

if (!string.IsNullOrEmpty(result))

{

LogManager.GetLogger().InfoEx("DoHmCdssHttpPost-url" + url + "result" + result + "cost" + stciadt.TotalMilliseconds + "");

}

else

{

LogManager.GetLogger().InfoEx("DoHmCdssHttpPost-url" + url + "失败/空结果, 耗时" + stciadt.TotalMilliseconds + "");

}

}

return result;

}

  1. 关键优化点解释

req.ServicePoint.Expect100Continue = false;‌

作用‌:禁止客户端在发送 Body 前等待服务器的 100-Continue 确认。

效果‌:对于 POST 请求,这能显著减少延迟,特别是在超时时间很短(如 1500ms)的场景下,这是解决"莫名超时"的关键技巧之一。

增加 Timeout 和添加 ReadWriteTimeout‌

Timeout 建议至少设置为 3000-5000ms,除非在极局域网环境。

ReadWriteTimeout 控制读取响应数据时的超时,防止服务器发送数据慢导致的挂起。

正确的资源释放 (finally + using)‌

原代码中 resp 没有关闭。如果异常发生,连接不会回到连接池,导致后续请求因等待连接而超时。

finally 块中调用 resp.Close() 是必须的。

关于 DefaultConnectionLimit‌

如果这是一个高并发场景,建议在应用程序启动时全局设置:

System.Net.ServicePointManager.DefaultConnectionLimit = 50; // 或更高

默认值 2 很容易成为瓶颈。

  1. 长期建议:迁移到 HttpClient

HttpWebRequest 已被微软标记为过时(Obsolete)。HttpClient 性能更好,默认支持连接池复用,且 API 更现代。

HttpClient 等效写法:‌

private static readonly HttpClient _httpClient = new HttpClient(); // 单例模式

public async Task<string> DoHmCdssHttpPostAsync(string url, string postData, string sKey)

{

var content = new StringContent(postData, Encoding.UTF8, "application/json");

_httpClient.DefaultRequestHeaders.Add("Huimei_id", sKey);

// 设置超时

_httpClient.Timeout = TimeSpan.FromSeconds(5);

try

{

var response = await _httpClient.PostAsync(url, content);

response.EnsureSuccessStatusCode();

return await response.Content.ReadAsStringAsync();

}

catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)

{

// 处理超时

throw new TimeoutException("请求超时");

}

}

相关推荐
He BianGu1 小时前
【WPF-Control教程】IOC 依赖注入
c#·wpf·wpf-control
你看我名字1 小时前
数眼智能 Search + Reader 实战:联网问答怎样避免“搜到了,却引用错了”
android·java·开发语言
小嘉丶学长1 小时前
Day13 · 列表与元组
linux·运维·服务器·开发语言·python
努力努力再努力wz2 小时前
【Lua入门系列】从 C++ 迭代器到 Lua 迭代器:一文深入理解 Lua 有状态以及无状态迭代器与泛型for、闭包、ipairs、pairs 和 next
开发语言·c++·lua
He BianGu2 小时前
【WPF-Control教程】H.Extensions.Behvaiors 开发文档
c#·wpf·wpf-control
努力实干2 小时前
第二阶段 · 第 7 课:C++ 多线程与线程安全
开发语言·c++·安全
爱聊灵光的维C2 小时前
C语言初阶:关于函数设计的详细讲解和注意事项
c语言·开发语言·青少年编程·蓝桥杯·学习方法
He BianGu2 小时前
【WPF-Control教程】H.Style 开发文档
c#·wpf·wpf-control
熊猫钓鱼>_>2 小时前
上架审核常见驳回与隐私合规清单:过审前先自查这 8 条【鸿蒙心迹】
开发语言·前端·华为·华为云·harmonyos·openharmony·ai制作