Wenn meine Schnittstelle Task zurückgeben muss, wie kann eine Implementierung ohne Operation am besten durchgeführt werden?

435

Im folgenden Code LazyBarmuss die Klasse aufgrund der Schnittstelle eine Aufgabe von ihrer Methode zurückgeben (und kann aus Gründen der Argumentation nicht geändert werden). Wenn die LazyBarImplementierung insofern ungewöhnlich ist, als sie schnell und synchron ausgeführt wird, wie kann eine No-Operation-Task am besten von der Methode zurückgegeben werden?

Ich habe mit gegangen Task.Delay(0)unten, aber ich würde gerne wissen, ob dies irgendwelche Performance Nebenwirkungen hat , wenn die Funktion aufgerufen wird , eine Menge (für Argumente willen, sagen mehrere hundert Mal pro Sekunde):

  • Entspannt sich dieser syntaktische Zucker zu etwas Großem?
  • Verstopft es den Thread-Pool meiner Anwendung?
  • Reicht das Compiler-Cleaver aus, um Delay(0)anders damit umzugehen ?
  • Wäre return Task.Run(() => { });anders?

Gibt es einen besseren Weg?

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
quelle
8
Persönlich würde ich mitgehen Task.FromResult<object>(null).
CodesInChaos

Antworten:

624

Das Verwenden von Task.FromResult(0)oder Task.FromResult<object>(null)verursacht weniger Overhead als das Erstellen eines TaskAusdrucks mit einem No-Op-Ausdruck. Beim Erstellen eines Taskmit einem vordefinierten Ergebnis ist kein Planungsaufwand erforderlich.


Heute würde ich empfehlen, Task.CompletedTask zu verwenden, um dies zu erreichen.

Reed Copsey
quelle
5
Und wenn Sie zufällig github.com/StephenCleary/AsyncEx verwenden , stellen sie eine TaskConstants-Klasse bereit, um diese abgeschlossenen Aufgaben zusammen mit mehreren anderen recht nützlichen Aufgaben bereitzustellen (0 int, true / false, Default <T> ())
quentin-starin
5
return default(YourReturnType);
Legenden
8
@ Legends Das funktioniert nicht zum direkten Erstellen einer Aufgabe
Reed Copsey
18
Task.CompletedTaskIch bin mir nicht sicher, könnte aber den Trick machen! (erfordert aber .net 4.6)
Peter
1
Frage: Wie kommt es, dass das Task.FromResult<TResult>, was a zurückgibt Task<TResult>, den Rückgabetyp von Task(keine Typparameter) erfüllt?
rory.ap
187

Um Reed Copseys Antwort zur Verwendung zu ergänzen Task.FromResult, können Sie die Leistung noch weiter verbessern, wenn Sie die bereits abgeschlossene Aufgabe zwischenspeichern, da alle Instanzen abgeschlossener Aufgaben gleich sind:

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

Mit können TaskExtensions.CompletedTaskSie dieselbe Instanz in der gesamten App-Domain verwenden.


Die neueste Version von .Net Framework (v4.6) fügt genau das mit der Task.CompletedTaskstatischen Eigenschaft hinzu

Task completedTask = Task.CompletedTask;
i3arnon
quelle
Muss ich zurückkehren es oder warten auf sie?
Pixar
@Pixar was meinst du? Sie können beides tun, aber das Warten wird synchron fortgesetzt.
i3arnon
Sorry, ich hatte den Kontext zu erwähnen :) Wie ich es jetzt sehen, können wir machen public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()sowie public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Also können wir entweder return CompletedTask;oder await CompletedTask;. Was ist vorzuziehen (vielleicht effizienter oder kongruenter)?
Pixar
3
@ Pixar Ich werde nicht klar. Ich meinte "'no-async' wäre effizienter". Wenn Sie eine Methode asynchronisieren, wird der Compiler angewiesen, sie in eine Zustandsmaschine umzuwandeln. Außerdem wird bei jedem Aufruf eine neue Aufgabe erstellt. Die Rückgabe einer bereits abgeschlossenen Aufgabe wäre klarer und leistungsfähiger.
i3arnon
3
@Asad reduziert die Zuweisungen (und damit die GC-Zeit). Anstatt jedes Mal, wenn Sie eine abgeschlossene Aufgabe benötigen, neuen Speicher zuzuweisen und eine Task-Instanz zu erstellen, tun Sie dies nur einmal.
i3arnon
38

Task.Delay(0)wie in der akzeptierten Antwort war ein guter Ansatz, da es sich um eine zwischengespeicherte Kopie eines abgeschlossenen handelt Task.

Ab 4.6 gibt es jetzt Task.CompletedTaskeinen expliziteren Zweck, der jedoch nicht nur Task.Delay(0)immer noch eine einzelne zwischengespeicherte Instanz zurückgibt , sondern auch dieselbe einzelne zwischengespeicherte Instanz wie Task.CompletedTask.

Die zwischengespeicherte Natur von beidem bleibt garantiert konstant, aber als implementierungsabhängige Optimierungen, die nur implementierungsabhängig sind, als Optimierungen (das heißt, sie würden immer noch korrekt funktionieren, wenn sich die Implementierung in etwas ändert, das noch gültig ist), Task.Delay(0)wurde verwendet besser als die akzeptierte Antwort.

Jon Hanna
quelle
1
Ich verwende immer noch 4.5 und als ich einige Nachforschungen anstellte, stellte ich amüsiert fest, dass Task.Delay (0) ein spezielles Gehäuse ist, um ein statisches CompletedTask-Mitglied zurückzugeben. Was ich dann in meinem eigenen statischen CompletedTask-Mitglied zwischengespeichert habe. : P
Darren Clark
2
Ich weiß nicht warum, Task.CompletedTaskkann aber nicht in PCL-Projekten verwendet werden, selbst wenn ich die .net-Version auf 4.6 (Profil 7) eingestellt habe, die gerade in VS2017 getestet wurde.
Felix
@Fay Ich würde vermuten, dass es nicht Teil der PCL-API-Oberfläche sein darf, obwohl das einzige, was im Moment irgendetwas tut, das PCL unterstützt, auch 4.5 unterstützt, also muss ich bereits meine eigene verwenden Task.CompletedTask => Task.Delay(0);, um das zu unterstützen, also ziehe ich an Ich weiß es nicht genau.
Jon Hanna
17

Vor kurzem ist dies aufgetreten und es wurden immer wieder Warnungen / Fehler bezüglich der ungültigen Methode angezeigt.

Wir haben es uns zur Aufgabe gemacht, den Compiler zu beruhigen, und das klärt es auf:

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

Dies vereint die bisher besten Ratschläge hier. Es ist keine return-Anweisung erforderlich, es sei denn, Sie tun tatsächlich etwas in der Methode.

Alexander Trauzzi
quelle
17
Das ist völlig falsch. Sie erhalten einen Compilerfehler, da die Methodendefinition asynchron ist und der Compiler ein Warten erwartet. Die "richtige" Verwendung wäre die öffentliche Aufgabe MyVoidAsyncMethog () {return Task.CompletedTask;}
Keith
3
Ich
bin
3
Wegen Keiths Kommentar.
Noelicus
4
Er liegt nicht ganz falsch, er hat nur das asynchrone Schlüsselwort entfernt. Mein Ansatz ist idiomatischer. Sein ist minimalistisch. Wenn nicht ein bisschen unhöflich.
Alexander Trauzzi
1
Das macht keinen Sinn, stimme Keith hier voll und ganz zu, ich bekomme eigentlich nicht alle Upvotes. Warum sollten Sie Code hinzufügen, der nicht erforderlich ist? public Task MyVoidAsyncMethod() {}ist völlig das gleiche wie oben Methode. Wenn es einen Anwendungsfall für diese Verwendung gibt, fügen Sie bitte den zusätzlichen Code hinzu.
Nick N.
12
return Task.CompletedTask; // this will make the compiler happy
Xin
quelle
9

Wenn Sie den angegebenen Typ zurückgeben müssen:

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

Ich bevorzuge die Task completedTask = Task.CompletedTask;Lösung von .Net 4.6, aber ein anderer Ansatz besteht darin, die Methode als asynchron zu markieren und void zurückzugeben:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Sie erhalten eine Warnung (CS1998 - Asynchrone Funktion ohne Ausdruck warten), die Sie in diesem Zusammenhang jedoch ignorieren können.

Remco te Wierik
quelle
1
Wenn Ihre Methode void zurückgibt, können Probleme mit Ausnahmen auftreten.
Adam Tuliper - MSFT
0

Wenn Sie Generika verwenden, geben uns alle Antworten einen Kompilierungsfehler. Sie können verwenden return default(T);. Beispiel unten zur weiteren Erläuterung.

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
quelle
Warum das Downvote?
Karthikeyan VK
Die Frage war nicht über asynchrone Methoden :)
Frode Nilsen
0
return await Task.FromResult(new MyClass());
JMH
quelle
3
Während dieser Code die Frage lösen kann, einschließlich einer Erklärung, wie und warum dies das Problem löst, würde dies wirklich dazu beitragen, die Qualität Ihres Beitrags zu verbessern, und wahrscheinlich zu mehr Up-Votes führen. Denken Sie daran, dass Sie in Zukunft die Frage für die Leser beantworten, nicht nur für die Person, die jetzt fragt. Bitte bearbeiten Sie Ihre Antwort, um Erklärungen hinzuzufügen und anzugeben, welche Einschränkungen und Annahmen gelten.
David Buck