You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 23:25:35