.NET Framework 4.8中HttpClient处理SSE时60秒超时求助
解决.NET Framework 4.8中SSE流读取60秒超时问题
问题根源
在.NET Framework 4.8中,Stream.ReadTimeout仅对同步读取操作生效,异步的ReadAsync不会遵循该设置。同时,HttpClient底层依赖的HttpWebRequest默认有60秒的读写超时,即便你设置了client.Timeout,未覆盖底层的ReadWriteTimeout仍会导致流读取超时。
解决方案
方案一:直接使用HttpWebRequest(推荐,更易控制超时)
直接操作HttpWebRequest,可以明确设置整个请求超时和流读写超时,两者对异步操作均生效:
private async Task<string> WaitForQueuePass(string bearer, string token) { string url = "https://blah.com"; var request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; // 设置整个请求的超时(2小时) request.Timeout = (int)TimeSpan.FromHours(2).TotalMilliseconds; // 设置流读写的超时(10分钟) request.ReadWriteTimeout = (int)TimeSpan.FromMinutes(10).TotalMilliseconds; request.Accept = "text/event-stream"; request.Headers.Add("Authorization", $"Bearer {bearer}"); using (var response = (HttpWebResponse)await request.GetResponseAsync()) using (var stream = response.GetResponseStream()) using (var reader = new StreamReader(stream, Encoding.UTF8)) { string line; // 按SSE规范逐行读取(SSE以行作为基本传输单元) while ((line = await reader.ReadLineAsync()) != null) { if (line.StartsWith("data:")) { string data = line.Substring(5).Trim(); if (data.Contains("passingToken")) { // 提取目标token return data.Split(new[] { "\"passingToken\":\"" }, StringSplitOptions.None)[1].Split('"')[0]; } } // 忽略SSE的空行或其他字段(如event:),可根据需求扩展处理 } } return string.Empty; }
方案二:配置HttpClient底层的HttpWebRequest超时
若坚持使用HttpClient,可通过自定义HttpClientHandler拦截并设置底层HttpWebRequest的读写超时:
private async Task<string> WaitForQueuePass(string bearer, string token) { string url = "https://blah.com"; using (var handler = new HttpClientHandler()) using (var client = new HttpClient(handler)) { client.Timeout = TimeSpan.FromHours(2); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearer}"); client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream")); // 拦截底层HttpWebRequest,设置读写超时 handler.HttpWebRequestCallback = request => { if (request is HttpWebRequest webRequest) { webRequest.ReadWriteTimeout = (int)TimeSpan.FromMinutes(10).TotalMilliseconds; } }; using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); using (var stream = await response.Content.ReadAsStreamAsync()) using (var reader = new StreamReader(stream, Encoding.UTF8)) { string line; while ((line = await reader.ReadLineAsync()) != null) { if (line.StartsWith("data:")) { string data = line.Substring(5).Trim(); if (data.Contains("passingToken")) { return data.Split(new[] { "\"passingToken\":\"" }, StringSplitOptions.None)[1].Split('"')[0]; } } } } } } return string.Empty; }
额外注意事项
- 按SSE规范逐行读取比直接读缓冲区更可靠,可避免消息被缓冲区截断的问题;
- 需确保服务器端未设置过短的连接超时,否则服务器主动断开连接会导致客户端读取中断;
- 若服务器发送心跳消息(如
data: \n\n),可在代码中处理心跳以维持连接,进一步避免超时。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

