Estoy intentando consultar el registro público de NPPES NPI usando Flurl como mi biblioteca de cliente HTTP, versión 2.4 .2.

Después de 4-5 solicitudes asíncronas exitosas (depuración al pasar por cada solicitud en el bucle), siempre falla con una SocketException de que la conexión se cerró por la fuerza. Supongo que la API tiene una tasa limitada, pero ralentizar las solicitudes no parece funcionar. Aquí está mi código relevante.

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

Y el bucle que lo llama con un sueño considerable entre solicitudes.

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

Aquí está la excepción real.

 ---> 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.  

¿Hay alguna forma más adecuada para que yo pueda depurar o hacer esto? Me imagino que necesitaría limitar el índice de clientes, pero ¿no debería un tiempo de espera generoso entre solicitudes funcionar al menos para la depuración?

1
tNAPA 2 ene. 2020 a las 21:03

2 respuestas

La mejor respuesta

El problema no es la aceleración de Flurl o API, sino el bloqueo de una llamada asincrónica (como dijo @WBuck).

La mejor práctica es evitar .Result e incluso .GetAwaiter().GetResult(). Esos y Task.Run() son todos ejemplos de mezcla de sincronización y código asíncrono. Hay buenos artículos en línea sobre por qué esto es malo: busque .net mix sync async para obtener más información de fondo.

La solución correcta es usar asíncrono "hasta el final". Casi todos los puntos de entrada se pueden marcar async ahora, incluidas las aplicaciones de consola.

Esto funciona en mi máquina incluso sin los durmientes:

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; }
    }
}
0
Nate Barbettini 2 ene. 2020 a las 19:19

No estás await haciendo la llamada a GetNpiEntry en la lambda Task.Run. Si la llamada a GetNpiEntry se ejecutó sincrónicamente, entonces no tiene problemas. Si la llamada a GetNpiEntry se ejecuta de forma asincrónica, entonces la Task.Run lambda no await su resultado.

Pruebe lo siguiente en su lugar:

foreach (DataRow row in npiTable.Rows)
{
    Result npiEntry = Task.Run( async () => await GetNpiEntry((string)row[0])).GetAwaiter( ).GetResult( );
    npiResults.Add(npiEntry);
    Thread.Sleep(2000);
}

También veo que estás usando .NetCore, por lo que deberías poder usar el siguiente main en su lugar.

static async Task Main( string[ ] args )

Esto le permite usar await dentro de Main.

1
WBuck 2 ene. 2020 a las 19:10