我正在尝试构建一个 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
您在
ReadToEnd
陷入僵局。您需要使用await ReadToEndAsync
。您还应该处理掉这些物品。
此外,如果出现 DNS 故障,
client.Timeout
有时不会超时,直到 15 秒结束。所以请使用 CancellationToken
来代替。
您还应该使用单个
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;
}