SocketException 现有连接被 Flurl 强行关闭
SocketException existing connection forcibly closed with Flurl
我正在尝试使用 Flurl 作为我的 HTTP 客户端库,版本 2.4.2 来查询 public NPPES NPI 注册表。
在 4-5 次成功的异步请求(通过循环中的每个请求进行调试)之后,它总是失败并出现连接被强制关闭的 SocketException。我假设 API 是速率受限的,但放慢请求速度似乎不起作用。这是我的相关代码。
static async Task<Result> GetNpiEntry(string npiNumber)
{
var npiEntry = await "https://npiregistry.cms.hhs.gov"
.AppendPathSegment("api/")
.SetQueryParams(new { version = "2.1", number = npiNumber }).GetJsonAsync<RootObject>();
return npiEntry.results[0];
}
并且循环在请求之间大量睡眠调用它。
List<Result> npiResults = new List<Result>(npiTable.Rows.Count);
foreach (DataRow row in npiTable.Rows)
{
Result npiEntry = Task.Run(() => GetNpiEntry((string)row[0])).Result;
npiResults.Add(npiEntry);
Thread.Sleep(2000);
}
这是真正的例外。
---> System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
---> System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.
有没有更适合我调试或执行此操作的方法?
我想我需要对客户端进行速率限制,但请求之间的大量等待时间至少对调试有用吗?
您没有在 Task.Run
lambda 中 await
调用 GetNpiEntry
。如果对 GetNpiEntry
的调用是同步执行的,那么就没有问题。如果对 GetNpiEntry
的调用异步执行,那么 Task.Run
lambda 将不会 await
它的结果。
请尝试以下操作:
foreach (DataRow row in npiTable.Rows)
{
Result npiEntry = Task.Run( async () => await GetNpiEntry((string)row[0])).GetAwaiter( ).GetResult( );
npiResults.Add(npiEntry);
Thread.Sleep(2000);
}
我还看到您正在使用 .NetCore
,因此您应该可以使用以下 main
。
static async Task Main( string[ ] args )
这允许您在 Main
中使用 await
。
问题不是 Flurl 或 API 节流,而是异步调用阻塞(如@WBuck 所说)。
最佳做法是避免 .Result
甚至 .GetAwaiter().GetResult()
。这些和 Task.Run()
都是混合同步和异步代码的例子。网上有很多很好的文章来解释为什么这是不好的 - 搜索 .net mix sync async
以获取更多背景信息。
正确的解决方案是使用异步 "all the way down"。现在几乎每个入口点都可以标记 async
,包括控制台应用程序。
即使 没有 睡眠,这也适用于我的机器:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
namespace SO_59567958
{
class Program
{
static async Task Main(string[] args)
{
var ids = new[] { "1023086709", "1659325215", "1912946427", "1740219450", "1750497640", "1538260823", "1275625626", "1144303488", "1205919107", "1730281890", "1568453561" };
Console.WriteLine($"Retrieving {ids.Length} NPI records...");
var npiResults = new List<Result>();
foreach (var id in ids)
{
var retrievedFromApi = await GetNpiEntry(id);
npiResults.Add(retrievedFromApi);
}
Console.WriteLine("Done");
npiResults.ForEach(x => Console.WriteLine(x.Number));
}
static async Task<Result> GetNpiEntry(string npiNumber)
{
var npiEntry = await "https://npiregistry.cms.hhs.gov"
.AppendPathSegment("api/")
.SetQueryParams(new { version = "2.1", number = npiNumber })
.GetJsonAsync<RootObject>();
return npiEntry.Results[0];
}
}
public class RootObject
{
public int ResultCount { get; set; }
public IReadOnlyList<Result> Results { get; set; }
}
public class Result
{
public int Number { get; set; }
}
}
我正在尝试使用 Flurl 作为我的 HTTP 客户端库,版本 2.4.2 来查询 public NPPES NPI 注册表。
在 4-5 次成功的异步请求(通过循环中的每个请求进行调试)之后,它总是失败并出现连接被强制关闭的 SocketException。我假设 API 是速率受限的,但放慢请求速度似乎不起作用。这是我的相关代码。
static async Task<Result> GetNpiEntry(string npiNumber)
{
var npiEntry = await "https://npiregistry.cms.hhs.gov"
.AppendPathSegment("api/")
.SetQueryParams(new { version = "2.1", number = npiNumber }).GetJsonAsync<RootObject>();
return npiEntry.results[0];
}
并且循环在请求之间大量睡眠调用它。
List<Result> npiResults = new List<Result>(npiTable.Rows.Count);
foreach (DataRow row in npiTable.Rows)
{
Result npiEntry = Task.Run(() => GetNpiEntry((string)row[0])).Result;
npiResults.Add(npiEntry);
Thread.Sleep(2000);
}
这是真正的例外。
---> System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
---> System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.
有没有更适合我调试或执行此操作的方法? 我想我需要对客户端进行速率限制,但请求之间的大量等待时间至少对调试有用吗?
您没有在 Task.Run
lambda 中 await
调用 GetNpiEntry
。如果对 GetNpiEntry
的调用是同步执行的,那么就没有问题。如果对 GetNpiEntry
的调用异步执行,那么 Task.Run
lambda 将不会 await
它的结果。
请尝试以下操作:
foreach (DataRow row in npiTable.Rows)
{
Result npiEntry = Task.Run( async () => await GetNpiEntry((string)row[0])).GetAwaiter( ).GetResult( );
npiResults.Add(npiEntry);
Thread.Sleep(2000);
}
我还看到您正在使用 .NetCore
,因此您应该可以使用以下 main
。
static async Task Main( string[ ] args )
这允许您在 Main
中使用 await
。
问题不是 Flurl 或 API 节流,而是异步调用阻塞(如@WBuck 所说)。
最佳做法是避免 .Result
甚至 .GetAwaiter().GetResult()
。这些和 Task.Run()
都是混合同步和异步代码的例子。网上有很多很好的文章来解释为什么这是不好的 - 搜索 .net mix sync async
以获取更多背景信息。
正确的解决方案是使用异步 "all the way down"。现在几乎每个入口点都可以标记 async
,包括控制台应用程序。
即使 没有 睡眠,这也适用于我的机器:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
namespace SO_59567958
{
class Program
{
static async Task Main(string[] args)
{
var ids = new[] { "1023086709", "1659325215", "1912946427", "1740219450", "1750497640", "1538260823", "1275625626", "1144303488", "1205919107", "1730281890", "1568453561" };
Console.WriteLine($"Retrieving {ids.Length} NPI records...");
var npiResults = new List<Result>();
foreach (var id in ids)
{
var retrievedFromApi = await GetNpiEntry(id);
npiResults.Add(retrievedFromApi);
}
Console.WriteLine("Done");
npiResults.ForEach(x => Console.WriteLine(x.Number));
}
static async Task<Result> GetNpiEntry(string npiNumber)
{
var npiEntry = await "https://npiregistry.cms.hhs.gov"
.AppendPathSegment("api/")
.SetQueryParams(new { version = "2.1", number = npiNumber })
.GetJsonAsync<RootObject>();
return npiEntry.Results[0];
}
}
public class RootObject
{
public int ResultCount { get; set; }
public IReadOnlyList<Result> Results { get; set; }
}
public class Result
{
public int Number { get; set; }
}
}