C #: Thread abandonne sans terminer les demandes restantes

Aug 18 2020

J'ai implémenté un planificateur qui envoie une demande à un point de terminaison tiers. Après avoir reçu la réponse, ma base de données locale est mise à jour avec la réponse. Actuellement, j'envoie plus de 50 000 demandes (1 000 demandes toutes les 10 minutes) et je traite la réponse. Le problème est parfois que le serveur tiers ne répond pas ou que la demande expire. Dans ce cas, j'obtiens une exception et le thread est abandonné sans traiter les demandes restantes. Ce dont j'ai besoin est de ne pas abandonner le thread et de passer à l'enregistrement suivant afin que l'enregistrement manqué soit traité dans un autre lot. Voici le code que j'utilise.

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

Réponses

aepot Aug 18 2020 at 14:38

Selon la HttpClientdocumentation:

HttpClient est destiné à être instancié une fois par application, plutôt que par utilisation.

HttpClientinstance par requête peut entraîner un épuisement du socket qui ne permet pas d'envoyer de nouvelles requêtes.

parfois le serveur tiers ne répond pas

Peut-être que le serveur tiers est OK, mais vos sockets ne le sont pas. Cela peut également entraîner l' ThreadAbortExceptionenvoi d'une nouvelle demande, en particulier si vous exécutez la demande de manière synchrone. getTask.Wait()est un appel sync-over-async qui n'est ni recommandé ni nécessaire ici.

Considérez ce code mis à jour à utiliser 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);
        }
    }
}

Remarque: si vous utilisez .Resultou .GetAwaiter().GetResult()non terminé Task, cela signifie que quelque chose s'est mal passé et qu'il y a une mauvaise pratique devant vous qui peut provoquer une impasse.

Le code ci-dessus peut être amélioré pour les demandes simultanées, par exemple envoyer tout en une seule fois ou gérer avec une limite active maximale à la fois. Mais avant tout, il est préférable de s'assurer que le code ci-dessus fonctionne.