Verwendung von ValueTask in C #

Die asynchrone Programmierung wird seit geraumer Zeit verwendet. In den letzten Jahren wurde es mit der Einführung von Async leistungsfähiger und wartet auf Schlüsselwörter. Sie können die asynchrone Programmierung nutzen, um die Reaktionsfähigkeit und den Durchsatz Ihrer Anwendung zu erhöhen.

Der empfohlene Rückgabetyp einer asynchronen Methode in C # ist Task. Sie sollten Task zurückgeben, wenn Sie eine asynchrone Methode schreiben möchten, die einen Wert zurückgibt. Wenn Sie einen Ereignishandler schreiben möchten, können Sie stattdessen void zurückgeben. Bis C # 7.0 konnte eine asynchrone Methode Task, Task oder void zurückgeben. Ab C # 7.0 kann eine asynchrone Methode auch ValueTask (verfügbar als Teil des Systems.Threading.Tasks.Extensions-Pakets) oder ValueTask zurückgeben. Dieser Artikel enthält eine Diskussion darüber, wie wir mit ValueTask in C # arbeiten können.

Um mit den in diesem Artikel bereitgestellten Codebeispielen arbeiten zu können, muss Visual Studio 2019 auf Ihrem System installiert sein. Wenn Sie noch keine Kopie haben, können Sie Visual Studio 2019 hier herunterladen.

Erstellen Sie ein .NET Core-Konsolenanwendungsprojekt in Visual Studio

Zunächst erstellen wir ein .NET Core-Konsolenanwendungsprojekt in Visual Studio. Angenommen, Visual Studio 2019 ist auf Ihrem System installiert, führen Sie die folgenden Schritte aus, um ein neues .NET Core-Konsolenanwendungsprojekt in Visual Studio zu erstellen.

  1. Starten Sie die Visual Studio-IDE.
  2. Klicken Sie auf "Neues Projekt erstellen".
  3. Wählen Sie im Fenster "Neues Projekt erstellen" aus der Liste der angezeigten Vorlagen "Konsolen-App (.NET Core)" aus.
  4. Weiter klicken.
  5. Geben Sie im nächsten Fenster "Konfigurieren Sie Ihr neues Projekt" den Namen und den Speicherort für das neue Projekt an.
  6. Klicken Sie auf Erstellen.

Dadurch wird ein neues .NET Core-Konsolenanwendungsprojekt in Visual Studio 2019 erstellt. In diesem Projekt wird die Verwendung von ValueTask in den folgenden Abschnitten dieses Artikels veranschaulicht.

Warum sollte ich ValueTask verwenden?

Eine Aufgabe repräsentiert den Status einer Operation, dh ob die Operation abgeschlossen, abgebrochen usw. ist. Eine asynchrone Methode kann entweder eine Task oder eine ValueTask zurückgeben.

Da Task ein Referenztyp ist, bedeutet das Zurückgeben eines Task-Objekts von einer asynchronen Methode, dass das Objekt bei jedem Aufruf der Methode dem verwalteten Heap zugewiesen wird. Eine Einschränkung bei der Verwendung von Task besteht daher darin, dass Sie bei jeder Rückgabe eines Task-Objekts von Ihrer Methode Speicher im verwalteten Heap zuweisen müssen. Wenn das Ergebnis der von Ihrer Methode ausgeführten Operation sofort verfügbar ist oder synchron abgeschlossen wird, ist diese Zuordnung nicht erforderlich und wird daher kostspielig.

Genau hier kommt ValueTask zur Rettung. ValueTask bietet zwei Hauptvorteile. Erstens verbessert ValueTask die Leistung, da keine Heap-Zuweisung erforderlich ist, und zweitens ist die Implementierung einfach und flexibel. Indem Sie ValueTask anstelle von Task von einer asynchronen Methode zurückgeben, wenn das Ergebnis sofort verfügbar ist, können Sie den unnötigen Zuordnungsaufwand vermeiden, da "T" hier eine Struktur darstellt und eine Struktur in C # ein Werttyp ist (im Gegensatz zu "T"). in Task, die eine Klasse darstellt).

Task und ValueTask repräsentieren zwei primäre "erwartete" Typen in C #. Beachten Sie, dass Sie eine ValueTask nicht blockieren können. Wenn Sie blockieren müssen, sollten Sie die ValueTask mithilfe der AsTask-Methode in eine Task konvertieren und dann dieses Referenz-Task-Objekt blockieren.

Beachten Sie auch, dass jede ValueTask nur einmal verwendet werden kann. Hier impliziert das Wort "verbrauchen", dass eine ValueTask asynchron auf den Abschluss der Operation warten (warten) oder AsTask nutzen kann, um eine ValueTask in eine Task zu konvertieren. Eine ValueTask sollte jedoch nur einmal verwendet werden. Danach sollte die ValueTask ignoriert werden.

ValueTask-Beispiel in C #

Angenommen, Sie haben eine asynchrone Methode, die eine Aufgabe zurückgibt. Sie können Task.FromResult nutzen, um das Task-Objekt wie im folgenden Code-Snippet gezeigt zu erstellen.

öffentliche Aufgabe GetCustomerIdAsync ()

{

    return Task.FromResult (1);

}}

Das obige Code-Snippet erstellt nicht die gesamte Magie der asynchronen Zustandsmaschine, sondern ordnet ein Task-Objekt im verwalteten Heap zu. Um diese Zuordnung zu vermeiden, möchten Sie möglicherweise stattdessen eine ValueTask verwenden, wie im folgenden Code-Snippet gezeigt.

public ValueTask GetCustomerIdAsync ()

{

    neue ValueTask (1) zurückgeben;

}}

Das folgende Codeausschnitt zeigt eine synchrone Implementierung von ValueTask.

 öffentliche Schnittstelle IRepository

    {

        ValueTask GetData ();

    }}

Die Repository-Klasse erweitert die IRepository-Schnittstelle und implementiert ihre Methoden wie unten gezeigt.

    Repository der öffentlichen Klasse: IRepository

    {

        public ValueTask GetData ()

        {

            var value = default (T);

            return new ValueTask (Wert);

        }}

    }}

So können Sie die GetData-Methode über die Main-Methode aufrufen.

statische Leere Main (string [] args)

        {

            IRepository Repository = neues Repository ();

            var result = repository.GetData ();

            if (result.IsCompleted)

                 Console.WriteLine ("Vorgang abgeschlossen ...");

            sonst

                Console.WriteLine ("Operation unvollständig ...");

            Console.ReadKey ();

        }}

Fügen wir nun unserem Repository eine weitere Methode hinzu, diesmal eine asynchrone Methode namens GetDataAsync. So würde die modifizierte IRepository-Schnittstelle aussehen.

öffentliche Schnittstelle IRepository

    {

        ValueTask GetData ();

        ValueTask GetDataAsync ();

    }}

Die GetDataAsync-Methode wird von der Repository-Klasse implementiert, wie im folgenden Code-Snippet gezeigt.

    Repository der öffentlichen Klasse: IRepository

    {

        public ValueTask GetData ()

        {

            var value = default (T);

            return new ValueTask (Wert);

        }}

        public async ValueTask GetDataAsync ()

        {

            var value = default (T);

            warte auf Task.Delay (100);

            Rückgabewert;

        }}

    }}

Wann sollte ich ValueTask in C # verwenden?

Obwohl ValueTask die Vorteile bietet, gibt es bestimmte Nachteile bei der Verwendung von ValueTask anstelle von Task. ValueTask ist ein Werttyp mit zwei Feldern, während Task ein Referenztyp mit einem einzelnen Feld ist. Die Verwendung einer ValueTask bedeutet daher, mit mehr Daten zu arbeiten, da ein Methodenaufruf zwei Datenfelder anstelle von einem zurückgeben würde. Wenn Sie auf eine Methode warten, die eine ValueTask zurückgibt, ist auch die Zustandsmaschine für diese asynchrone Methode größer, da sie im Fall einer Aufgabe eine Struktur aufnehmen müsste, die zwei Felder anstelle einer einzelnen Referenz enthält.

Wenn der Benutzer einer asynchronen Methode Task.WhenAll oder Task.WhenAny verwendet, kann die Verwendung von ValueTask als Rückgabetyp in einer asynchronen Methode kostspielig werden. Dies liegt daran, dass Sie die ValueTask mithilfe der AsTask-Methode in Task konvertieren müssten, was zu einer Zuordnung führen würde, die leicht vermieden werden könnte, wenn eine zwischengespeicherte Task überhaupt verwendet worden wäre.

Hier ist die Faustregel. Verwenden Sie Task, wenn Sie einen Code haben, der immer asynchron ist, dh wenn der Vorgang nicht sofort abgeschlossen wird. Nutzen Sie ValueTask, wenn das Ergebnis einer asynchronen Operation bereits verfügbar ist oder wenn Sie bereits ein zwischengespeichertes Ergebnis haben. In jedem Fall sollten Sie die erforderliche Leistungsanalyse durchführen, bevor Sie ValueTask in Betracht ziehen.

So machen Sie mehr in C #:

  • Verwendung der Unveränderlichkeit in C.
  • Verwendung von const, readonly und static in C #
  • Verwendung von Datenanmerkungen in C #
  • So arbeiten Sie mit GUIDs in C # 8
  • Wann wird eine abstrakte Klasse im Vergleich zur Schnittstelle in C # verwendet?
  • So arbeiten Sie mit AutoMapper in C #
  • Verwendung von Lambda-Ausdrücken in C #
  • So arbeiten Sie mit Action-, Func- und Predicate-Delegaten in C #
  • So arbeiten Sie mit Delegierten in C #
  • So implementieren Sie einen einfachen Logger in C #
  • So arbeiten Sie mit Attributen in C #
  • So arbeiten Sie mit log4net in C #
  • So implementieren Sie das Repository-Entwurfsmuster in C #
  • Wie man mit Reflexion in C # arbeitet
  • So arbeiten Sie mit dem Dateisystemwatcher in C #
  • So führen Sie eine verzögerte Initialisierung in C # durch
  • So arbeiten Sie mit MSMQ in C #
  • So arbeiten Sie mit Erweiterungsmethoden in C #
  • Wie wir Lambda-Ausdrücke in C #
  • Wann wird das flüchtige Schlüsselwort in C # verwendet?
  • Verwendung des Schlüsselwortsield in C #
  • So implementieren Sie Polymorphismus in C #
  • So erstellen Sie Ihren eigenen Taskplaner in C #
  • So arbeiten Sie mit RabbitMQ in C #
  • So arbeiten Sie mit einem Tupel in C #
  • Erkundung virtueller und abstrakter Methoden in C #
  • Verwendung des Dapper ORM in C #
  • Verwendung des Entwurfsmusters im Fliegengewicht in C #