38 votes

Une tâche <T> .Convertir <TResult> méthode d'extension être utile ou at-il des dangers cachés?

Je suis en train d'écrire les bibliothèques client pour Google Api Cloud qui ont un motif assez commun pour async helper surcharges:

  • Faire quelques brèves synchrone de travail pour définir une requête
  • Faire une requête asynchrone
  • Transformer le résultat d'une manière simple

Actuellement, nous sommes l'aide de méthodes asynchrones pour ça, mais:

  • Transformer le résultat de l'attendent finit par être gênant en termes de préséance - nous finissons par avoir besoin d' (await foo.Bar().ConfigureAwait(false)).TransformToBaz() et les crochets sont ennuyeux. À l'aide de deux états améliore la lisibilité, mais signifie que nous ne pouvons pas utiliser une expression à corps de la méthode.
  • Parfois, nous oublions ConfigureAwait(false) - ce qui est soluble avec de l'outillage, dans une certaine mesure, mais il est encore un peu de l'odeur

Task<TResult>.ContinueWith sonne comme une bonne idée, mais j'ai lu de Stephen Cleary du blog recommander contre elle, et les raisons semblent sonore. Nous envisageons d'ajouter une méthode d'extension pour Task<T> comme ceci:

Le potentiel de la méthode d'extension

public static async Task<TResult> Convert<TSource, TResult>(
    this Task<TSource> task, Func<TSource, TResult> projection)
{
    var result = await task.ConfigureAwait(false);
    return projection(result);
}

On peut alors appeler ce à partir d'une méthode synchrone vraiment très simplement, par exemple

public async Task<Bar> BarAsync()
{
    var fooRequest = BuildFooRequest();
    return FooAsync(fooRequest).Convert(foo => new Bar(foo));
}

ou encore:

public Task<Bar> BarAsync() =>
    FooAsync(BuildFooRequest()).Convert(foo => new Bar(foo));

Cela semble si simple et utile que je suis un peu surpris de voir qu'il n'est pas quelque chose de déjà disponibles.

Comme un exemple de cas où je voudrais l'utiliser pour faire une expression corsé méthode de travail, en Google.Cloud.Translation.V2 code j'ai deux méthodes pour traduire du texte simple: on prend une chaîne unique et qu'on en prend plusieurs chaînes de caractères. Les trois options pour l'unique chaîne de version (simplifié quelque peu en termes de paramètres):

Régulière méthode async

public async Task<TranslationResult> TranslateTextAsync(
    string text, string targetLanguage)
{
    GaxPreconditions.CheckNotNull(text, nameof(text));
    var results = await TranslateTextAsync(new[] { text }, targetLanguage).ConfigureAwait(false);
    return results[0];
}

L'Expression des corps méthode async

public async Task<TranslationResult> TranslateTextAsync(
    string text, string targetLanguage) =>
    (await TranslateTextAsync(new[] { GaxPreconditions.CheckNotNull(text, nameof(text)) }, targetLanguage)
        .ConfigureAwait(false))[0];

L'Expression des corps méthode de synchronisation à l'aide de Convertir

public Task<TranslationResult> TranslateTextAsync(
    string text, string targetLanguage) =>
    TranslateTextAsync(new[] { GaxPreconditions.CheckNotNull(text, nameof(text)) }, targetLanguage)
        .Convert(results => results[0]);

Personnellement, je préfère le dernier de ceux-ci.

Je suis conscient que cela change au moment de la validation - dans le dernier exemple, le passage d'un null valeur text sera immédiatement jeter un ArgumentNullException alors que le passage d'un null valeur targetLanguage sera de retour d'une reproché à la tâche (car TranslateTextAsync échouent de manière asynchrone). C'est une différence, je suis prêt à accepter.

Existe t il des différences dans la planification ou de la performance que je devrais être au courant? (Nous sommes encore la construction de deux machines d'état, parce que l' Convert méthode permet de créer un. À l'aide de Task.ContineWith permettrait d'éviter que tous les problèmes mentionnés dans le billet de blog. L' Convert méthode pourrait être modifié pour utiliser ContinueWith attentivement.)

(Je suis un peu tenté de poster ceci sur CodeReview, mais je soupçonne que les informations contenues dans les réponses seront plus utiles en général au-delà de savoir si c'est une bonne idée. Si d'autres personnes sont en désaccord, je suis heureux de le déplacer.)

24voto

Stephen Cleary Points 91731

Transformer le résultat de l'attendent finit par être gênant en termes de préséance

Je préfère généralement à introduire un local var, mais comme vous l'avez noté, qui empêche l'expression des corps des méthodes.

Parfois, nous oublions ConfigureAwait(false) - ce qui est soluble avec de l'outillage, dans une certaine mesure

Puisque vous travaillez sur une bibliothèque et devrait utiliser ConfigureAwait(false) partout, il peut être intéressant d'utiliser un analyseur de code qui applique ConfigureAwait d'utilisation. Il y a un ReSharper plugin et un VS plugin qui font cela. Je n'ai pas essayé moi-même.

Task<TResult>.ContinueWith sonne comme une bonne idée, mais j'ai lu de Stephen Cleary du blog recommander contre elle, et les raisons semblent sonore.

Si vous avez utilisé ContinueWith, vous devez spécifier explicitement TaskScheduler.Default (c'est l' ContinueWith équivalent de ConfigureAwait(false)), et aussi envisager d'ajouter des indicateurs tels que DenyChildAttach. IMO c'est plus difficile de se rappeler comment utiliser ContinueWith correctement qu'il est à retenir ConfigureAwait(false).

Sur l'autre main, tout en ContinueWith est un bas niveau, méthode dangereuse, si vous l'utilisez correctement, il peut vous donner des mineurs des améliorations de performances. En particulier, l'aide de l' state paramètre peut vous faire économiser de répartition des délégués. C'est l'approche couramment prises par le TPL et d'autres bibliothèques Microsoft, mais IMO il abaisse la maintenabilité trop pour la plupart des bibliothèques.

Cela semble si simple et utile que je suis un peu surpris de voir qu'il n'est pas quelque chose de déjà disponibles.

L' Convert méthode que vous suggérez a existé, de manière informelle, Then. Stephen ne le dit pas, mais je suppose que le nom de l' Then est à partir de la JavaScript monde, où sont les promesses de la tâche équivalent (ils sont les deux contrats à Terme).

Sur une note de côté, Stephen blog prend ce concept intéressant conclusion. Convert/Then est le bind pour l'Avenir de l'errance, de sorte qu'il peut être utilisé pour mettre en œuvre LINQ-sur-contrats à terme. Stephen Toub a également publié le code pour cette (assez datée à ce point, mais intéressant).

J'ai pensé à quelques reprises sur l'ajout d' Then de mon AsyncEx de la bibliothèque, mais à chaque fois il n'a pas fait la coupe, car c'est à peu près la même comme juste await. Son seul avantage est de résoudre le problème de priorité en permettant le chaînage de méthode. Je suppose qu'il n'existe pas dans le cadre de la même raison.

Cela dit, il n'y a certainement rien de mal avec la mise en œuvre de votre propre Convert méthode. Cela permettra d'éviter la parenthèse / extra locale variable et de permettre l'expression des corps des méthodes.

Je suis conscient que cela change au moment de la validation

C'est l'une des raisons pour lesquelles je suis méfiant de eliding async/await (mon blog va dans plus de raisons).

Dans ce cas, je pense que c'est bien de toute façon, depuis le "bref synchrone de travail pour définir une requête" est une des conditions préalables de vérifier, et de l'OMI, il n'a pas d'importance où boneheaded exceptions sont jetés (parce qu'ils ne devraient pas être pris de toute façon).

Si le "bref synchrone" est plus complexe - si c'était quelque chose qui pouvait le jeter, ou pourrait raisonnablement jeter après que quelqu'un refactors un an à partir de maintenant - alors je voudrais utiliser async/await. Vous pouvez toujours utiliser Convert afin d'éviter la priorité numéro:

public async Task<TranslationResult> TranslateTextAsync(string text, string targetLanguage) =>
  await TranslateTextAsync(SomthingThatCanThrow(text), targetLanguage)
  .Convert(results => results[0])
  .ConfigureAwait(false);

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