C #: il thread si interrompe senza completare le richieste rimanenti

Aug 18 2020

Ho implementato uno scheduler che invia la richiesta a un endpoint di terze parti. Dopo aver ricevuto la risposta, il mio database locale viene aggiornato con la risposta. Al momento, invio più di 50.000 richieste (1.000 richieste ogni 10 minuti) ed elaboro la risposta. Il problema a volte è che il server di terze parti non risponde o il timeout della richiesta. In questo caso ottengo un'eccezione e il thread viene interrotto senza elaborare le richieste rimanenti. Quello di cui ho bisogno è non interrompere il thread e passare al record successivo in modo che il record perso venga elaborato in un altro batch. Ecco il codice che sto usando.

public class ScheduledAPIJob : IJob
{
    public Task Execute(IJobExecutionContext context)
    {
        Task taskAPI = Task.Factory.StartNew(() => ProcessAPI());
        return taskAPI;
    }
    void ProcessAPI()
    {
        //Error logging object
        SchedulerLogWriter lw = new SchedulerLogWriter("Logs\\Scheduler");

        List<WeatherData> list = new List<WeatherData>();

        APIQueueBAL objBal = new APIQueueBAL();

        //List of endpoints to hit.
        var APIQueue = objBal.QueuedAPIs();

        foreach (var item in APIQueue)
        {
            try
            {
                var endpoint = item.FunctionParameters;
                HttpRequestHelper objRequestHelper = new HttpRequestHelper();
                
                //Response from API
                var response = objRequestHelper.GetAPIResponse(endpoint);
                
                ////Update local database.
                if (response.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    list = JsonConvert.DeserializeObject<List<WeatherData>>(response.Content.ReadAsStringAsync().Result);
                    objBal.ProcessWeatherData(item, list);
                }
            }
            catch (Exception ex)
            {
                lw.WriteLog(ex.Message);
                lw.WriteLog(Convert.ToString(ex.InnerException));
                lw.WriteLog(ex.StackTrace);
            }
        }
    }
}

public class HttpRequestHelper
{
    public HttpResponseMessage GetAPIResponse(string apiEndpoint)
    {
        using (var client = new HttpClient())
        {
            var getTask = client.GetAsync(apiEndpoint);
            getTask.Wait();
            return getTask.Result;
        }
    }
}

Risposte

aepot Aug 18 2020 at 14:38

Come da HttpClientdocumentazione:

HttpClient è concepito per essere istanziato una volta per applicazione, anziché per utilizzo.

HttpClientistanza per richiesta può causare l' esaurimento del socket che non rende possibile inviare nuove richieste.

a volte il server di terze parti non risponde

Forse il server di terze parti è OK ma i tuoi socket no. Può anche causare l' ThreadAbortExceptioninvio di una nuova richiesta soprattutto se si esegue la richiesta in modo sincrono. getTask.Wait()è una chiamata sync-over-async che non è consigliata e non necessaria qui.

Considera questo codice aggiornato da usare async/await.

public class ScheduledAPIJob : IJob
{
    public Task Execute(IJobExecutionContext context)
    {
        return ProcessAPI();
    }
    private async Task ProcessAPI()
    {
        //Error logging object
        SchedulerLogWriter lw = new SchedulerLogWriter("Logs\\Scheduler");

        APIQueueBAL objBal = new APIQueueBAL();

        //List of endpoints to hit.
        var APIQueue = objBal.QueuedAPIs();

        foreach (var item in APIQueue)
        {
            try
            {
                string endpoint = item.FunctionParameters;

                //Response from API
                List<WeatherData> list = await HttpRequestHelper.GetAPIResponseAsync<List<WeatherData>>(endpoint);
                objBal.ProcessWeatherData(item, list);
            }
            catch (Exception ex)
            {
                lw.WriteLog(ex.Message);
                lw.WriteLog(Convert.ToString(ex.InnerException));
                lw.WriteLog(ex.StackTrace);
            }
        }
    }
}

public static class HttpRequestHelper
{
    private static readonly HttpClient client = new HttpClient();

    public static async Task<T> GetAPIResponseAsync<T>(string apiEndpoint)
    {
        using (HttpResponseMessage response = await client.GetAsync(apiEndpoint, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
        {
            response.EnsureSuccessStatusCode(); // throws if not success
            string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            return JsonConvert.DeserializeObject<T>(json);
        }
    }
}

Nota: se stai usando .Resulto .GetAwaiter().GetResult()per non completato Task, significa che qualcosa è andato storto e c'è una cattiva pratica davanti a te che potrebbe causare un deadlock.

Il codice sopra può essere migliorato per richieste simultanee, ad esempio inviare tutto in una volta o gestire con il limite massimo attivo in una volta. Ma prima è meglio assicurarsi che il codice sopra funzioni.