使用 HttpClient 和 C 语言中的超时进行完全异常处理#

Full exception handling with HttpClient and timeouts in C#

提问人:nilsakesson 提问时间:8/11/2023 最后编辑:nilsakesson 更新时间:8/11/2023 访问量:393

问:

我正在尝试构建一个 wpf 应用程序,该应用程序将充当某些设备的子网扫描程序。一些 IP 地址将回复响应,而其他 IP 地址将由于没有收到请求而超时。我正在使用 Task.WhenAll() 异步执行所有请求,当达到 HttpClient.Timeout 时,任务将按预期取消,并且 UI 仍具有响应性。一段时间后,UI 冻结,HttpRequestExceptions 和 SocketExceptions 从已取消的线程中出现。我确实尝试使用取消令牌,但行为相同。 是否可以取消来自 HttpClient 的完整请求,或者是否可以更好地处理异常以使 UI 不冻结?

这是带有一个 url 示例的代码,该 url 可以正常工作,并且不会回复和超时。

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);
            }
        }
    }
}

以及随之而来的例外。可以看出,HttpRequestException 和 SocketException 是在取消任务之后出现的。

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
C# .NET 异常 超时 HttpClient

评论

0赞 Charlieface 8/11/2023
当你说“UI冻结”时,你的意思是在调试器中,你得到一个First Chance Exception弹出窗口吗?如果是这样,这是正常的,你可以禁用该异常类型的首次机会异常,以便它不会停止调试器。
0赞 nilsakesson 8/14/2023
当 taskCanceledExceptions 来自超时时时,状态日志中的所有更新都会按应有的方式更新。比如说,大约 10 秒后,当 HttpRequestExceptions 出现时,按钮不可单击,并且应用中的徘徊操作没有响应。禁用 First Chansce Exception 时也是如此。

答:

0赞 Charlieface 8/11/2023 #1

你陷入了僵局。您需要使用 .您还应该处理这些对象。ReadToEndawait ReadToEndAsync

此外,如果发生 DNS 故障,有时直到 15 秒才超时。因此,请改用 a。client.TimeoutCancellationToken

此外,您应该使用单个 ,以避免套接字耗尽。HttpClient

static HttpClient _client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) };

public async Task<string> GetStatus(string url)
{
    using var cts = new cancellationTokeSource(2000);
    using var HTTPResponse = await _client.GetStreamAsync(url, cts.Token);
    using var sr = new StreamReader(HTTPResponse);
    string response = await sr.ReadToEndAsync(cts.Token);
    return response;
}

评论

0赞 nilsakesson 8/11/2023
即使进行了所有这些更改,TaskCanceledException 也是正在处理的更改,并且 HttpRequestException 和 SocketException 正在冻结 UI 线程。cancelationToken 不会取消请求,因此即使它是 DNS 问题,它似乎也无法解决它。