Si mon interface doit renvoyer Task, quelle est la meilleure façon d'avoir une implémentation sans opération?

436

Dans le code ci-dessous, en raison de l'interface, la classe LazyBardoit renvoyer une tâche à partir de sa méthode (et pour des arguments, le sake ne peut pas être changé). Si l' LazyBarimplémentation de S est inhabituelle en ce sens qu'elle s'exécute rapidement et de manière synchrone - quelle est la meilleure façon de renvoyer une tâche sans opération à partir de la méthode?

Je suis allé avec Task.Delay(0)ci - dessous, mais je voudrais savoir si cela a des effets secondaires performances si la fonction est appelée beaucoup (pour l' amour des arguments, dire des centaines de fois par seconde):

  • Ce sucre syntaxique se déroule-t-il en quelque chose de grand?
  • Cela commence-t-il à obstruer le pool de threads de mon application?
  • Le couperet du compilateur est-il suffisant pour traiter Delay(0)différemment?
  • Serait- return Task.Run(() => { });ce différent?

Y a-t-il une meilleure façon?

using System.Threading.Tasks;

namespace MyAsyncTest
{
    internal interface IFooFace
    {
        Task WillBeLongRunningAsyncInTheMajorityOfImplementations();
    }

    /// <summary>
    /// An implementation, that unlike most cases, will not have a long-running
    /// operation in 'WillBeLongRunningAsyncInTheMajorityOfImplementations'
    /// </summary>
    internal class LazyBar : IFooFace
    {
        #region IFooFace Members

        public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
        {
            // First, do something really quick
            var x = 1;

            // Can't return 'null' here! Does 'Task.Delay(0)' have any performance considerations?
            // Is it a real no-op, or if I call this a lot, will it adversely affect the
            // underlying thread-pool? Better way?
            return Task.Delay(0);

            // Any different?
            // return Task.Run(() => { });

            // If my task returned something, I would do:
            // return Task.FromResult<int>(12345);
        }

        #endregion
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Test();
        }

        private static async void Test()
        {
            IFooFace foo = FactoryCreate();
            await foo.WillBeLongRunningAsyncInTheMajorityOfImplementations();
            return;
        }

        private static IFooFace FactoryCreate()
        {
            return new LazyBar();
        }
    }
}
Jon Rea
la source
8
Personnellement, j'irais avec Task.FromResult<object>(null).
CodesInChaos

Réponses:

626

L'utilisation de Task.FromResult(0)ou Task.FromResult<object>(null)entraînera moins de frais généraux que la création d'un Taskavec une expression sans op. Lors de la création d'un Taskavec un résultat prédéterminé, aucune surcharge de planification n'est impliquée.


Aujourd'hui, je recommanderais d'utiliser Task.CompletedTask pour y parvenir.

Reed Copsey
la source
5
Et si vous utilisez github.com/StephenCleary/AsyncEx, ils fournissent une classe TaskConstants pour fournir ces tâches terminées ainsi que plusieurs autres assez utiles (0 int, true / false, Default <T> ())
quentin-starin
5
return default(YourReturnType);
Legends
8
@Legends Cela ne fonctionne pas pour créer une tâche directement
Reed Copsey
18
je ne suis pas sûr mais Task.CompletedTaskpourrait faire l'affaire! (mais nécessite .net 4.6)
Peter
187

Pour compléter la réponse de Reed Copsey à propos de l'utilisation Task.FromResult, vous pouvez encore améliorer les performances si vous mettez en cache la tâche déjà terminée car toutes les instances de tâches terminées sont les mêmes:

public static class TaskExtensions
{
    public static readonly Task CompletedTask = Task.FromResult(false);
}

Avec, TaskExtensions.CompletedTaskvous pouvez utiliser la même instance dans l'ensemble du domaine d'application.


La dernière version du .Net Framework (v4.6) ajoute juste cela avec la Task.CompletedTaskpropriété statique

Task completedTask = Task.CompletedTask;
i3arnon
la source
Dois-je le retourner ou l' attendre ?
Pixar
@Pixar que voulez-vous dire? Vous pouvez faire les deux, mais l'attendre se poursuivra de manière synchrone.
i3arnon
Désolé, j'ai dû mentionner le contexte :) Comme je le vois maintenant, nous pouvons faire public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()aussi bien que public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Donc, nous pouvons soit return CompletedTask;ou await CompletedTask;. Quoi de plus préférable (peut-être plus efficace ou plus congruent)?
Pixar
3
@Pixar, je ne veux pas clair. Je voulais dire "'no-async' serait plus efficace". Faire une méthode asynchrone demande au compilateur de la transformer en machine à états. Il créera également une nouvelle tâche à chaque appel. Renvoyer une tâche déjà terminée serait plus clair et plus performant.
i3arnon
3
@Asad, il réduit les allocations (et avec lui le temps GC). Au lieu d'allouer de la nouvelle mémoire et de construire une instance de tâche chaque fois que vous avez besoin d'une tâche terminée, vous ne le faites qu'une seule fois.
i3arnon
38

Task.Delay(0)comme dans la réponse acceptée était une bonne approche, car il s'agit d'une copie mise en cache d'un terminé Task.

À partir de la version 4.6, il existe désormais un objet Task.CompletedTaskplus explicite, mais non seulement il Task.Delay(0)renvoie toujours une seule instance en cache, mais il renvoie la même instance en cache unique que lui Task.CompletedTask.

La nature en cache de n'est garanti à rester constant, mais comme Optimisations dépendant de l' implémentation qui ne dépendent que la mise en œuvre Optimisations (qui est, ils avaient toujours correctement si la mise en œuvre a changé quelque chose qui était encore valide) l'utilisation de Task.Delay(0)était mieux que la réponse acceptée.

Jon Hanna
la source
1
J'utilise toujours 4.5 et lorsque j'ai fait des recherches, j'ai été amusé de constater que Task.Delay (0) est un boîtier spécial pour renvoyer un membre statique CompletedTask. Que j'ai ensuite mis en cache dans mon propre membre statique CompletedTask. : P
Darren Clark
2
Je ne sais pas pourquoi, mais Task.CompletedTaskne peut pas être utilisé dans un projet PCL, même si j'ai défini la version .net sur 4.6 (profil 7), juste testée dans VS2017.
Felix
@Fay, je suppose que cela ne doit pas faire partie de la surface de l'API PCL, bien que la seule chose à faire pour le moment qui prend en charge PCL prend également en charge 4.5, donc je dois déjà utiliser le mien Task.CompletedTask => Task.Delay(0);pour le supporter, donc je ne sais pas du haut de ma tête.
Jon Hanna
17

J'ai récemment rencontré cela et j'ai continué à recevoir des avertissements / erreurs concernant l'annulation de la méthode.

Nous sommes en train de calmer le compilateur et cela clarifie les choses:

    public async Task MyVoidAsyncMethod()
    {
        await Task.CompletedTask;
    }

Cela rassemble le meilleur de tous les conseils jusqu'ici. Aucune déclaration de retour n'est nécessaire à moins que vous ne fassiez réellement quelque chose dans la méthode.

Alexander Trauzzi
la source
17
C'est complètement faux. Vous obtenez une erreur de compilation car la définition de méthode contient async, donc le compilateur attend une attente. L'utilisation "correcte" serait la tâche publique MyVoidAsyncMethog () {return Task.CompletedTask;}
Keith
3
Je ne sais pas pourquoi cela a été rejeté, car cela semble être la réponse la plus
claire
3
À cause du commentaire de Keith.
noelicus
4
Il n'a pas tout à fait tort, il vient de supprimer le mot-clé async. Mon approche est plus idiomatique. Le sien est minimaliste. Sinon un peu grossier.
Alexander Trauzzi
1
Cela n'a aucun sens, complètement d'accord avec Keith ici, je ne reçois pas tous les votes positifs en fait. Pourquoi voudriez-vous ajouter du code qui n'est pas nécessaire? public Task MyVoidAsyncMethod() {}est complètement la même que la méthode ci-dessus. S'il existe un cas d'utilisation pour l'utiliser comme ceci, veuillez ajouter le code supplémentaire.
Nick N.18
12
return Task.CompletedTask; // this will make the compiler happy
Xin
la source
9

Lorsque vous devez renvoyer le type spécifié:

Task.FromResult<MyClass>(null);
trashmaker_
la source
3

Je préfère la Task completedTask = Task.CompletedTask;solution de .Net 4.6, mais une autre approche consiste à marquer la méthode async et à retourner void:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Vous obtiendrez un avertissement (CS1998 - Fonction asynchrone sans expression d'attente), mais cela peut être ignoré dans ce contexte.

Remco te Wierik
la source
1
Si votre méthode retourne vide, vous pouvez avoir des problèmes avec des exceptions.
Adam Tuliper - MSFT
0

Si vous utilisez des génériques, toutes les réponses nous donneront une erreur de compilation. Vous pouvez utiliser return default(T);. Échantillon ci-dessous pour expliquer davantage.

public async Task<T> GetItemAsync<T>(string id)
            {
                try
                {
                    var response = await this._container.ReadItemAsync<T>(id, new PartitionKey(id));
                    return response.Resource;
                }
                catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {

                    return default(T);
                }

            }
Karthikeyan VK
la source
Pourquoi le downvote?
Karthikeyan VK
La question n'était pas sur les méthodes asynchrones :)
Frode Nilsen
0
return await Task.FromResult(new MyClass());
JMH
la source
3
Bien que ce code puisse résoudre la question, y compris une explication de comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre message, et entraînerait probablement plus de votes positifs. N'oubliez pas que vous répondrez à la question des lecteurs à l'avenir, pas seulement à la personne qui pose la question maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limitations et hypothèses applicables.
David Buck