Se la mia interfaccia deve restituire Task, qual è il modo migliore per avere un'implementazione senza operazioni?


436

Nel codice seguente, a causa dell'interfaccia, la classe LazyBardeve restituire un'attività dal suo metodo (e per ragioni di amore non può essere modificata). Se l' LazyBarimplementazione è insolita in quanto accade che venga eseguita rapidamente e in modo sincrono, qual è il modo migliore per restituire un'attività senza operazioni dal metodo?

Sono andato di Task.Delay(0)seguito, tuttavia vorrei sapere se questo ha effetti collaterali sulle prestazioni se la funzione è chiamata molto (per amor di argomenti, diciamo centinaia di volte al secondo):

  • Questo zucchero sintattico si scioglie in qualcosa di grosso?
  • Inizia a ostruire il pool di thread della mia applicazione?
  • La mannaia del compilatore è abbastanza per gestire Delay(0)diversamente?
  • Sarebbe return Task.Run(() => { });diverso?

Esiste un modo migliore?

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


8
Personalmente ci andrei Task.FromResult<object>(null).
CodesInChaos,

Risposte:


626

L'uso Task.FromResult(0)o Task.FromResult<object>(null)comporta un sovraccarico minore rispetto alla creazione di un Taskcon un'espressione no-op. Quando si crea un Taskcon un risultato predeterminato, non sono previsti costi generali di pianificazione.


Oggi, consiglierei di utilizzare Task.CompletedTask per raggiungere questo obiettivo.


5
E se ti capita di usare github.com/StephenCleary/AsyncEx , forniscono una classe TaskConstants per fornire queste attività completate insieme a molte altre abbastanza utili (0 int, true / false, Default <T> ())
quentin-starin

5
return default(YourReturnType);
Legends

8
@Legends che non funziona per la creazione diretta di un'attività
Reed Copsey

18
non sono sicuro, ma Task.CompletedTaskpotrebbe fare il trucco! (ma richiede .net 4.6)
Peter

187

Per aggiungere alla risposta di Reed Copsey sull'utilizzo Task.FromResult, è possibile migliorare ulteriormente le prestazioni se si memorizza nella cache l'attività già completata poiché tutte le istanze delle attività completate sono uguali:

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

Con TaskExtensions.CompletedTaskte puoi usare la stessa istanza in tutto il dominio dell'app.


L' ultima versione di .Net Framework (v4.6) aggiunge proprio questo con la Task.CompletedTaskproprietà statica

Task completedTask = Task.CompletedTask;

Devo restituirlo o attendere su di esso?
Pixar,

@Pixar cosa intendi? Puoi fare entrambe le cose, ma in attesa continuerà in modo sincrono.
i3arnon,

Ci dispiace, ho dovuto menzionare il contesto :) Come lo vedo ora, possiamo fare public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()pure public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Quindi, possiamo o return CompletedTask;o await CompletedTask;. Cosa è più preferibile (forse più efficiente o più congruente)?
Pixar,

3
@Pixar Non voglio chiarire. Volevo dire "'no-async' sarebbe più efficiente". Fare un metodo asincrono indica al compilatore di trasformarlo in una macchina a stati. Creerà anche una nuova attività ogni volta che la chiami. Restituire un'attività già completata sarebbe più chiaro e più performante.
i3arnon,

3
@Asad riduce le allocazioni (e con esso il tempo GC). Invece di allocare nuova memoria e costruire un'istanza Task ogni volta che hai bisogno di un'attività completata, lo fai solo una volta.
i3arnon,

38

Task.Delay(0)come nella risposta accettata è stato un buon approccio, in quanto è una copia cache di un completato Task.

A partire da 4.6 c'è ora Task.CompletedTaskche è più esplicito nel suo scopo, ma non solo Task.Delay(0)restituisce ancora una singola istanza memorizzata nella cache, ma restituisce la stessa singola istanza memorizzata nella cache Task.CompletedTask.

La natura cache di nessuno dei due è garantita per rimanere costante, ma come ottimizzazioni dipendenti dall'implementazione che dipendono solo dall'implementazione come ottimizzazioni (ovvero, funzionerebbero comunque correttamente se l'implementazione fosse cambiata in qualcosa che era ancora valido) l'uso di Task.Delay(0)era meglio della risposta accettata.


1
Sto ancora usando 4.5 e quando ho fatto qualche ricerca mi sono divertito a scoprire che Task.Delay (0) è un caso speciale per restituire un membro statico CompletedTask. Che ho quindi memorizzato nella cache nel mio membro CompletedTask statico. : P
Darren Clark,

2
Non so perché, ma Task.CompletedTasknon posso essere utilizzato nel progetto PCL, anche se ho impostato la versione .net su 4.6 (profilo 7), appena testata in VS2017.
Felix

@Fay immagino che non debba far parte della superficie dell'API PCL, anche se al momento l'unica cosa che fa qualcosa che supporta PCL supporta anche 4.5, quindi devo già usare il mio Task.CompletedTask => Task.Delay(0);per supportarlo, quindi lo so per certo dalla cima della mia testa.
Jon Hanna,

17

Recentemente riscontrato questo e continuato a ricevere avvisi / errori sul metodo che è nullo.

Ci occupiamo di placare il compilatore e questo chiarisce:

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

Questo riunisce il meglio di tutti i consigli qui finora. Non è necessaria alcuna dichiarazione di ritorno a meno che tu non stia effettivamente facendo qualcosa nel metodo.


17
Questo è completamente sbagliato. Stai ricevendo un errore del compilatore perché la definizione del metodo contiene asincrono, quindi il compilatore si aspetta un'attesa. L'uso "corretto" sarebbe Attività pubblica MyVoidAsyncMethog () {return Task.CompletedTask;}
Keith

3
Non sono sicuro del motivo per cui questo è stato votato perché questa sembra essere la risposta più pulita
Webwake,

3
A causa del commento di Keith.
Noelicus,

4
Non ha del tutto torto, ha appena rimosso la parola chiave asincrona. Il mio approccio è più idiomatico. Il suo è minimalista. Se non un po 'maleducato.
Alexander Trauzzi,

1
Questo non ha senso, sono completamente d'accordo con Keith qui, in realtà non ricevo tutti i voti. Perché dovresti aggiungere codice che non è necessario? public Task MyVoidAsyncMethod() {}è completamente uguale al metodo sopra. Se esiste un caso d'uso per usarlo in questo modo, si prega di aggiungere il codice aggiuntivo.
Nick N.

12
return Task.CompletedTask; // this will make the compiler happy

9

Quando è necessario restituire il tipo specificato:

Task.FromResult<MyClass>(null);

3

Preferisco la Task completedTask = Task.CompletedTask;soluzione di .Net 4.6, ma un altro approccio è quello di contrassegnare il metodo asincrono e restituire il vuoto:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Riceverai un avviso (CS1998 - Funzione asincrona senza attendere l'espressione), ma è sicuro ignorarlo in questo contesto.


1
Se il metodo restituito è nullo, è possibile riscontrare problemi con le eccezioni.
Adam Tuliper - MSFT,

0

Se stai usando generici, tutte le risposte ci daranno un errore di compilazione. È possibile utilizzare return default(T);. Esempio sotto per spiegare ulteriormente.

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);
                }

            }

Perché il downvote?
Karthikeyan VK

La domanda non riguardava i metodi asincroni :)
Frode Nilsen,

0
return await Task.FromResult(new MyClass());

3
Sebbene questo codice possa risolvere la domanda, inclusa una spiegazione di come e perché questo risolva il problema, contribuirebbe davvero a migliorare la qualità del tuo post e probabilmente darebbe più voti positivi. Ricorda che stai rispondendo alla domanda per i lettori in futuro, non solo per la persona che chiede ora. Si prega di modificare la risposta per aggiungere spiegazioni e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni.
David Buck,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.