C# WPF中HttpClient超时与异常处理:UI冻结问题求助
问题
开发WPF子网扫描器时,部分IP请求超时,用Task.WhenAll()执行异步请求,超时任务触发TaskCanceledException后UI暂时响应,但一段时间后冻结,同时已取消的线程出现HttpRequestException和SocketException。尝试用CancellationToken未解决问题,需求是彻底取消HttpClient请求或优化异常处理避免UI冻结。
示例代码:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Windows; namespace TestWPF { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } public async Task<string> GetStatus(string url) { using HttpClient client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(2); try { var HTTPResponse = await client.GetStreamAsync(url); string response = new System.IO.StreamReader(HTTPResponse).ReadToEnd(); return response; } catch (Exception e) { throw; } } private async void Button_Click(object sender, RoutedEventArgs e) { List<Task> tasks = new List<Task> { GetStatus("https://api.thecatapi.com/v1/images/search"), GetStatus("https://192.168.0.31") }; try { await Task.WhenAll(tasks); } catch (Exception ex) { Debug.WriteLine($"Exception: {ex}"); } // Loop results var successfulTasks = tasks.Where(task => task.Status == TaskStatus.RanToCompletion); // Process the results of successful tasks foreach (var successfulTask in successfulTasks) { // Get the return value inside the task string scanResult = ((Task<string>)successfulTask).Result; Debug.WriteLine(scanResult); } } } }
异常信息:
Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Net.Http.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in TestWPF.dll Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll Exception: System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 2 seconds elapsing. ---> System.TimeoutException: A task was canceled. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled. at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpClient.GetStreamAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken) --- End of inner exception stack trace --- --- End of inner exception stack trace --- at System.Net.Http.HttpClient.HandleFailure(Exception e, Boolean telemetryStarted, HttpResponseMessage response, CancellationTokenSource cts, CancellationToken cancellationToken, CancellationTokenSource pendingRequestsCts) at System.Net.Http.HttpClient.GetStreamAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken) at TestWPF.MainWindow.GetStatus(String url) at TestWPF.MainWindow.Button_Click(Object sender, RoutedEventArgs e) [{"id":"9l6","url":"https://cdn2.thecatapi.com/images/9l6.jpg","width":720,"height":960}] The thread 0x3a0c has exited with code 0 (0x0). Exception thrown: 'System.Net.Sockets.SocketException' in System.Net.Sockets.dll Exception thrown: 'System.Net.Sockets.SocketException' in System.Private.CoreLib.dll Exception thrown: 'System.Net.Http.HttpRequestException' in System.Net.Http.dll Exception thrown: 'System.Net.Http.HttpRequestException' in System.Private.CoreLib.dll Exception thrown: 'System.Net.Http.HttpRequestException' in System.Private.CoreLib.dll Exception thrown: 'System.Net.Http.HttpRequestException' in System.Private.CoreLib.dll
解决方案
1. 复用HttpClient实例
每个请求创建新HttpClient会导致Socket资源耗尽,这是UI冻结的核心原因之一。HttpClient是线程安全的,全局复用一个实例即可。
2. 结合CancellationToken控制超时
单独用HttpClient.Timeout无法彻底终止底层Socket连接,配合CancellationTokenSource可以更可靠地取消请求,同时避免资源泄漏。
3. 精准捕获异常
不要捕获所有Exception,针对性处理TaskCanceledException(超时/取消)、HttpRequestException、SocketException,避免异常扩散占用UI线程资源。
4. 避免使用Task.Result
改用await获取任务结果,Result会阻塞线程,直接导致UI冻结。
5. 限制并发请求数
子网扫描时并发请求过多会占用大量系统资源,用SemaphoreSlim控制并发量,平衡扫描效率和UI响应性。
修改后的代码示例:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace TestWPF { public partial class MainWindow : Window { // 全局复用HttpClient实例 private static readonly HttpClient _httpClient = new HttpClient(); // 控制并发请求数,根据硬件配置调整 private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(10); public MainWindow() { InitializeComponent(); // 初始化全局超时 _httpClient.Timeout = TimeSpan.FromSeconds(2); } public async Task<string> GetStatus(string url, CancellationToken cancellationToken) { await _semaphore.WaitAsync(cancellationToken); try { using var responseStream = await _httpClient.GetStreamAsync(url, cancellationToken); using var reader = new System.IO.StreamReader(responseStream); return await reader.ReadToEndAsync(cancellationToken); } catch (TaskCanceledException) { Debug.WriteLine($"请求超时或取消: {url}"); return null; } catch (HttpRequestException ex) { Debug.WriteLine($"HTTP请求异常 {url}: {ex.Message}"); return null; } catch (SocketException ex) { Debug.WriteLine($"Socket异常 {url}: {ex.Message}"); return null; } finally { _semaphore.Release(); } } private async void Button_Click(object sender, RoutedEventArgs e) { var urls = new List<string> { "https://api.thecatapi.com/v1/images/search", "https://192.168.0.31" }; using var cts = new CancellationTokenSource(); // 可选:设置全局任务超时,优先级高于HttpClient.Timeout // cts.CancelAfter(TimeSpan.FromSeconds(2)); var tasks = urls.Select(url => GetStatus(url, cts.Token)).ToList(); await Task.WhenAll(tasks); // 处理成功返回的结果 foreach (var task in tasks) { var result = await task; if (!string.IsNullOrEmpty(result)) { Debug.WriteLine(result); } } } } }
关键说明
- 全局
HttpClient避免Socket资源泄漏,解决长期运行后的UI冻结问题。 SemaphoreSlim限制并发数,防止系统资源被过度占用。- 精准异常处理减少不必要的异常传播,降低UI线程负担。
- 全程使用
await替代Result,保证UI线程始终处于响应状态。
内容的提问来源于stack exchange,提问作者nilsakesson
相关产品推荐
相关产品推荐

