58 votes

Architecture pour async/await

Si vous utilisez async/await à un niveau inférieur dans votre architecture, il est nécessaire de "bulle" async/await appels tout en haut, est-il inefficace puisque vous êtes essentiellement la création d'un nouveau thread pour chaque couche (de manière asynchrone à l'appel d'une fonction asynchrone pour chaque couche, ou n'est-il pas vraiment d'importance et il est juste en fonction de votre préférence?

Je suis à l'aide d'EF 6.0-alpha3 afin que je puisse avoir de méthodes asynchrones en EF.

Mon référentiel est telle:

public class EntityRepository<E> : IRepository<E> where E : class
{
    public async virtual Task Save()
    {
        await context.SaveChangesAsync();
    }
}

Maintenant, ma couche est en tant que tel:

public abstract class ApplicationBCBase<E> : IEntityBC<E>
{
    public async virtual Task Save()
    {
        await repository.Save();
    }
}

Et puis, bien sûr, ma méthode dans mon UI aurait du suivre le même modèle lors de l'appel.

Est-ce:

  1. nécessaire
  2. négatif sur les performances
  3. juste une question de préférence

Même si ce n'est pas utilisée dans des couches distinctes/projets, la même question s'applique à si je fais appel imbriqué méthodes dans la même catégorie:

    private async Task<string> Dosomething1()
    {
        //other stuff 
        ...
        return await Dosomething2();
    }
    private async Task<string> Dosomething2()
    {
        //other stuff 
        ...
        return await Dosomething3();
    }
    private async Task<string> Dosomething3()
    {
        //other stuff 
        ...
        return await Task.Run(() => "");
    }

58voto

Jon Skeet Points 692016

Si vous utilisez async/await à un niveau inférieur dans votre architecture, il est nécessaire de "bulle" async/await appels tout en haut, est-il inefficace puisque vous êtes essentiellement la création d'un nouveau thread pour chaque couche (de manière asynchrone à l'appel d'une fonction asynchrone pour chaque couche, ou n'est-il pas vraiment d'importance et il est juste en fonction de votre préférence?

Cette question suggère un couple de zones d'incompréhension.

Tout d'abord, vous n'avez pas créer un nouveau thread à chaque fois que vous appelez une fonction d'asynchrone.

Deuxièmement, vous n'avez pas besoin de déclarer une méthode asynchrone, juste parce que vous êtes à l'appel d'une fonction asynchrone. Si vous êtes heureux avec la tâche qui est déjà retourné, tout juste de retour qu'à partir d'une méthode qui ne veut pas avoir l'async modificateur:

public class EntityRepository<E> : IRepository<E> where E : class
{
    public virtual Task Save()
    {
        return context.SaveChangesAsync();
    }
}

public abstract class ApplicationBCBase<E> : IEntityBC<E>
{
    public virtual Task Save()
    {
        return repository.Save();
    }
}

Cela va être un peu plus efficace, car elle n'implique pas une machine de l'etat créé pour très peu de raison - mais plus important encore, c'est plus simple.

Toute méthode async où vous avez un seul await expression dans l'attente d'une Task ou Task<T>, droite à la fin de la méthode sans traitement supplémentaire, serait mieux d'être écrit sans l'aide de async/await. Donc ceci:

public async Task<string> Foo()
{
    var bar = new Bar();
    bar.Baz();
    return await bar.Quux();
}

est mieux écrit que:

public Task<string> Foo()
{
    var bar = new Bar();
    bar.Baz();
    return bar.Quux();
}

(En théorie, il y a une très légère différence dans les tâches en cours de création et, par conséquent, que les appelants pourraient ajouter des continuations, mais dans la grande majorité des cas, vous ne remarquerez aucune différence.)

28voto

Reed Copsey Points 315315

est-il inefficace puisque vous êtes essentiellement la création d'un nouveau thread pour chaque couche (de manière asynchrone à l'appel d'une fonction asynchrone pour chaque couche, ou n'est-il pas vraiment d'importance et il est juste en fonction de votre préférence?

Pas de. Méthodes asynchrones ne sont pas nécessairement utiliser de nouveaux threads. Dans ce cas, puisque le sous-jacent appel de méthode asynchrone est un IO lié méthode, il faudrait vraiment pas de nouveaux threads créés.

Est-ce:

1. necessary

Il est nécessaire de "bulle" les appels asynchrones si vous souhaitez conserver l'opération asynchrone. C'est vraiment préféré, cependant, car il vous permet de profiter pleinement de toutes les méthodes asynchrones, y compris la composition de leur ensemble à travers l'ensemble de la pile.

2. negative on performance

Pas de. Comme je l'ai mentionné, ce n'est pas de créer de nouveaux threads. Il y a une surcharge, mais beaucoup de ce qui peut être réduite (voir ci-dessous).

3. just a matter of preference

Pas si vous voulez garder cette asynchrone. Vous avez besoin de faire garder les choses asynchrone dans la pile.

Maintenant, il ya certaines choses que vous pouvez faire pour améliorer les perf. ici. Si vous êtes juste envelopper une méthode asynchrone, vous n'avez pas besoin d'utiliser les fonctionnalités de la langue - il suffit de retourner l' Task:

public virtual Task Save()
{
    return repository.Save();
}

L' repository.Save() méthode retourne déjà un Task - vous n'avez pas besoin d'attendre juste pour l'envelopper de retour dans un Task. Cela permet de garder la méthode un peu plus efficace.

Vous pouvez aussi avoir votre "bas niveau" des méthodes asynchrones utiliser ConfigureAwait pour les empêcher d'avoir besoin de l'appeler contexte de synchronisation:

private async Task<string> Dosomething2()
{
    //other stuff 
    ...
    return await Dosomething3().ConfigureAwait(false);
}

Cela réduit considérablement les coûts de chaque await si vous n'avez pas besoin de s'inquiéter sur le contexte de l'appel. Ce est généralement la meilleure option lorsque vous travaillez sur la "bibliothèque" du code, depuis "l'extérieur" await la capture à l'INTERFACE utilisateur du contexte. Le "intérieure" fonctionnement de la bibliothèque n'a généralement pas de soins sur la synchronisation contexte, il est donc préférable de ne pas capturer.

Enfin, je l'avais mise en garde contre l'un de vos exemples:

private async Task<string> Dosomething3()
{
    //other stuff 
    ...
    // Potentially a bad idea!
    return await Task.Run(() => "");
}

Si vous effectuez une méthode async qui, en interne, est à l'aide de Task.Run de "créer de l'asynchronie" autour de quelque chose qui n'est pas lui-même asynchrone, vous êtes effectivement emballage synchrone code dans une méthode asynchrone. Ce sera l'utilisation d'un pool de threads thread, mais peut "cacher" le fait qu'il est le faire, ce qui fait de l'API trompeuse. Il est souvent préférable de laisser l'appel à Task.Run pour votre plus haut niveau des appels, et de laisser le sous-jacent méthodes rester synchrone sauf si elles sont vraiment en mesure de profiter de l'asynchrone IO ou certains moyens de déchargement autre que Task.Run. (Ce n'est pas toujours vrai, mais "async" code enveloppé sur code synchrone via Task.Run, alors retourné par async/await est souvent le signe d'un défaut de conception.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X