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