web-dev-qa-db-fra.com

Si mon interface doit renvoyer Task, quel est le meilleur moyen d’avoir une implémentation sans opération?

Dans le code ci-dessous, en raison de l'interface, la classe LazyBar doit renvoyer une tâche à partir de sa méthode (et pour des arguments sake ne peut pas être modifié). Si l'implémentation de LazyBars est inhabituelle en ce sens qu'elle s'exécute rapidement et de manière synchrone - quel est le meilleur moyen de renvoyer une tâche sans opération à partir de la méthode?

Je suis allé avec Task.Delay(0) ci-dessous, mais j'aimerais savoir si cela a des effets indésirables sur les performances si la fonction s'appelle un lot (par souci d'argument, disons des centaines de fois par seconde ):

  • Est-ce que ce sucre syntaxique retourne à quelque chose de grand?
  • Commence-t-il à obstruer le pool de threads de mon application?
  • Le couperet du compilateur est-il suffisant pour traiter Delay(0) différemment?
  • Est-ce que return Task.Run(() => { }); serait différent?

Y a-t-il un meilleur moyen?

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();
        }
    }
}
373
Jon Rea

L'utilisation de Task.FromResult(0) ou Task.FromResult<object>(null) occasionnera moins de temps système que la création d'un Task avec une expression sans opération. Lors de la création d'un Task avec un résultat prédéterminé, aucun temps système supplémentaire n'est impliqué dans la planification.


Aujourd'hui, je recommanderais d'utiliser Task.CompletedTask pour accomplir cela.

539
Reed Copsey

Pour ajouter à réponse de Reed Copsey à propos de l'utilisation de _Task.FromResult_, vous pouvez améliorer les performances encore davantage si vous mettez en cache la tâche déjà terminée car toutes les instances de tâches terminées sont identiques:

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

Avec _TaskExtensions.CompletedTask_, vous pouvez utiliser la même instance dans l’ensemble du domaine de l’application.


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

_Task completedTask = Task.CompletedTask;
_
173
i3arnon

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 Task terminé.

À partir de la version 4.6, il y a maintenant Task.CompletedTask qui est plus explicite dans son but, mais non seulement Task.Delay(0) renvoie toujours une seule instance mise en cache, il retourne l'instance identique unique mise en cache, comme Task.CompletedTask.

La nature en cache de ni est garantie de rester constante, mais en tant qu'optimisations dépendantes de l'implémentation qui dépendent uniquement de l'implémentation (c'est-à-dire qu'elles fonctionneraient toujours correctement si l'implémentation changeait pour quelque chose qui était toujours valide) Task.Delay(0) était mieux que la réponse acceptée.

33
Jon Hanna

Récemment rencontré ceci et continuait à recevoir des avertissements/erreurs sur la méthode étant annulée.

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

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

Cela rassemble le meilleur de tous les conseils donnés jusqu'ici. Aucune déclaration de retour n'est nécessaire sauf si vous faites réellement quelque chose dans la méthode.

14
Alexander Trauzzi

Quand vous devez retourner le type spécifié:

Task.FromResult<MyClass>(null);
4
trashmaker_

Je préfère la solution Task completedTask = Task.CompletedTask; de .Net 4.6, mais une autre approche consiste à marquer la méthode de manière asynchrone et à renvoyer une valeur nulle:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Vous recevrez un avertissement (CS1998 - Fonction asynchrone sans expression d'attente), mais il est prudent de l'ignorer dans ce contexte.

3
Remco te Wierik
return Task.CompletedTask; // this will make the compiler happy
3
Xin