Utiliser Async et Await pour séparer l'appel de base de données (avec Dapper)

async-await asynchronous c# dapper

Question

Nous demandons des milliers d'objets à Dapper et atteignons la limite du paramètre (2100). Nous avons donc décidé de les charger en morceaux.

Je pensais que ce serait une bonne occasion d'essayer Async Await - c'est la première fois que j'essayais, peut-être en train de faire une erreur d'écolier!

Les points d'arrêt sont touchés, mais tout ne revient pas. Ce n'est pas une erreur - il semble que tout se passe dans un trou noir!

Aidez-moi, s'il vous plaît!

C'était ma méthode originale - il appelle maintenant la méthode Async

    public List<MyObject> Get(IEnumerable<int> ids)
    {
        return this.GetMyObjectsAsync(ids).Result.ToList();
    }  //Breakpoint on this final bracket never gets hit

J'ai ajouté cette méthode pour diviser les identifiants en blocs de 1000, puis attendre la fin des tâches

    private async Task<List<MyObject>> GetMyObjectsAsync(IEnumerable<int> ids)
    {
        var subSets = this.Partition(ids, 1000);

        var tasks = subSets.Select(set => GetMyObjectsTask(set.ToArray()));

        //breakpoint on the line below gets hit ...
        var multiLists = await Task.WhenAll(tasks);

        //breakpoint on line below never gets hit ...
        var list = new List<MyObject>();
        foreach (var myobj in multiLists)
        {
            list.AddRange(myobj);   
        }
        return list;
    }

et ci-dessous est la tâche ...

    private async Task<IEnumerable<MyObject>> GetMyObjectsTask(params int[] ids)
    {
        using (var db = new SqlConnection(this.connectionString))
        {
            //breakpoint on the line below gets hit
            await db.OpenAsync();
            return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN @Ids",
            new { ids});
        }
    }

La méthode suivante divise simplement la liste des identifiants en morceaux - cela semble fonctionner correctement ...

    private IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int size)
    {
        var partition = new List<T>(size);
        var counter = 0;

        using (var enumerator = source.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                partition.Add(enumerator.Current);
                counter++;
                if (counter % size == 0)
                {
                    yield return partition.ToList();
                    partition.Clear();
                    counter = 0;
                }
            }

            if (counter != 0)
                yield return partition;
        }
    }

Réponse acceptée

Vous créez une situation de blocage comme vous utilisez async/await en combinaison avec la Task<T>.Result .

La ligne incriminée est la suivante:

return this.GetMyObjectsAsync(ids).Result.ToList();

GetMyObjectsAsync(ids).Result bloque le contexte de synchronisation en cours. Mais GetMyObjectsAsync utilisations await que les horaires d' un point de continuation pour le contexte de synchronisation en cours. Je suis sûr que vous pouvez voir le problème avec cette approche: la continuation ne peut jamais être exécutée car le contexte de synchronisation actuel est bloqué par Task.Result .

Une solution qui peut fonctionner dans certains cas serait d'utiliser ConfigureAwait(false) ce qui signifie que la continuation peut être exécutée dans n'importe quel contexte de synchronisation.

Mais en général, je pense qu'il vaut mieux éviter Task.Result avec async/await Task.Result .


Notez que cette situation de blocage dépend du contexte de synchronisation utilisé lors de l'appel de la méthode asynchrone. Pour ASP.net, Windows Forms et WPF, cela se traduira par un blocage, mais pour autant que je sache, il ne le sera pas pour une application console. (Merci à Marc Gravell pour son commentaire)


Microsoft propose un bon article sur les meilleures pratiques en matière de programmation asynchrone . (Merci à Ken2k)


Réponse populaire

Je pense que les paramètres sont sensibles à la casse, il devrait être:

return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN @ids",
            new { ids});

au lieu de "@Ids" ci-dessous dans la requête:

 return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN **@Ids**",
            new { ids});

Je ne sais pas si mais essayez.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi