Come usare ValueTask in C #

La programmazione asincrona è in uso da un po 'di tempo ormai. Negli ultimi anni è stato reso più potente con l'introduzione delle parole chiave async e await. Puoi sfruttare la programmazione asincrona per aumentare la velocità di risposta e la velocità effettiva dell'applicazione.

Il tipo restituito consigliato di un metodo asincrono in C # è Task. È necessario restituire Task se si desidera scrivere un metodo asincrono che restituisca un valore. Se desideri scrivere un gestore di eventi, puoi invece restituire void. Fino a C # 7.0 un metodo asincrono poteva restituire Task, Task o void. A partire da C # 7.0, un metodo asincrono può anche restituire ValueTask (disponibile come parte del pacchetto System.Threading.Tasks.Extensions) o ValueTask. Questo articolo presenta una discussione su come lavorare con ValueTask in C #.

Per lavorare con gli esempi di codice forniti in questo articolo, dovresti avere Visual Studio 2019 installato nel tuo sistema. Se non hai già una copia, puoi scaricare Visual Studio 2019 qui.

Creare un progetto di applicazione console .NET Core in Visual Studio

Prima di tutto, creiamo un progetto di applicazione console .NET Core in Visual Studio. Supponendo che Visual Studio 2019 sia installato nel sistema, seguire i passaggi descritti di seguito per creare un nuovo progetto di applicazione console .NET Core in Visual Studio.

  1. Avvia l'IDE di Visual Studio.
  2. Fai clic su "Crea nuovo progetto".
  3. Nella finestra "Crea nuovo progetto", seleziona "App Console (.NET Core)" dall'elenco dei modelli visualizzati.
  4. Fare clic su Avanti.
  5. Nella finestra "Configura il tuo nuovo progetto" mostrata di seguito, specifica il nome e la posizione per il nuovo progetto.
  6. Fare clic su Crea.

Questo creerà un nuovo progetto di applicazione console .NET Core in Visual Studio 2019. Useremo questo progetto per illustrare l'uso di ValueTask nelle sezioni successive di questo articolo.

Perché dovrei usare ValueTask?

Un'attività rappresenta lo stato di alcune operazioni, ovvero se l'operazione è stata completata, annullata e così via. Un metodo asincrono può restituire un'attività o un ValueTask.

Ora, poiché Task è un tipo di riferimento, la restituzione di un oggetto Task da un metodo asincrono implica l'allocazione dell'oggetto sull'heap gestito ogni volta che viene chiamato il metodo. Pertanto, un avvertimento nell'utilizzo di Task è che è necessario allocare memoria nell'heap gestito ogni volta che si restituisce un oggetto Task dal metodo. Se il risultato dell'operazione eseguita dal metodo è disponibile immediatamente o viene completato in modo sincrono, questa allocazione non è necessaria e quindi diventa costosa.

Qui è esattamente dove ValueTask viene in soccorso. ValueTask offre due vantaggi principali. Innanzitutto, ValueTask migliora le prestazioni perché non necessita di allocazione dell'heap e, in secondo luogo, è facile e flessibile da implementare. Restituendo ValueTask invece di Task da un metodo asincrono quando il risultato è immediatamente disponibile, puoi evitare il sovraccarico non necessario dell'allocazione poiché "T" qui rappresenta una struttura e una struttura in C # è un tipo di valore (in contrasto con la "T" in Task, che rappresenta una classe).

Task e ValueTask rappresentano due tipi principali "in attesa" in C #. Notare che non è possibile bloccare in un ValueTask. Se è necessario bloccare, convertire ValueTask in un'attività utilizzando il metodo AsTask e quindi bloccare su tale oggetto Task di riferimento.

Si noti inoltre che ogni ValueTask può essere utilizzato solo una volta. Qui la parola "consumare" implica che un ValueTask può attendere in modo asincrono il completamento dell'operazione o sfruttare AsTask per convertire un ValueTask in un Task. Tuttavia, un ValueTask dovrebbe essere utilizzato una sola volta, dopodiché il ValueTask dovrebbe essere ignorato.

Esempio di ValueTask in C #

Supponi di avere un metodo asincrono che restituisce un Task. Potresti sfruttare Task.FromResult per creare l'oggetto Task come mostrato nello snippet di codice riportato di seguito.

Attività pubblica GetCustomerIdAsync ()

{

    return Task.FromResult (1);

}

Il frammento di codice precedente non crea l'intera magia della macchina a stati asincrona ma alloca un oggetto Task nell'heap gestito. Per evitare questa allocazione, potresti voler sfruttare un ValueTask invece come mostrato nello snippet di codice riportato di seguito.

ValueTask pubblico GetCustomerIdAsync ()

{

    restituire nuovo ValueTask (1);

}

Il frammento di codice seguente illustra un'implementazione sincrona di ValueTask.

 interfaccia pubblica IRepository

    {

        ValueTask GetData ();

    }

La classe Repository estende l'interfaccia IRepository e implementa i suoi metodi come mostrato di seguito.

    Repository della classe pubblica: IRepository

    {

        ValueTask pubblico GetData ()

        {

            valore var = predefinito (T);

            restituire nuovo ValueTask (valore);

        }

    }

Ecco come chiamare il metodo GetData dal metodo Main.

static void Main (string [] args)

        {

            Repository IRepository = new Repository ();

            var risultato = repository.GetData ();

            if (result.IsCompleted)

                 Console.WriteLine ("Operazione completata ...");

            altro

                Console.WriteLine ("Operazione incompleta ...");

            Console.ReadKey ();

        }

Aggiungiamo ora un altro metodo al nostro repository, questa volta un metodo asincrono denominato GetDataAsync. Ecco come apparirà l'interfaccia IRepository modificata.

interfaccia pubblica IRepository

    {

        ValueTask GetData ();

        ValueTask GetDataAsync ();

    }

Il metodo GetDataAsync è implementato dalla classe Repository come mostrato nello snippet di codice riportato di seguito.

    Repository della classe pubblica: IRepository

    {

        ValueTask pubblico GetData ()

        {

            valore var = predefinito (T);

            restituire nuovo ValueTask (valore);

        }

        ValueTask asincrono pubblico GetDataAsync ()

        {

            valore var = predefinito (T);

            attende Task.Delay (100);

            valore di ritorno;

        }

    }

Quando dovrei usare ValueTask in C #?

Nonostante i vantaggi offerti da ValueTask, ci sono alcuni compromessi nell'utilizzo di ValueTask al posto di Task. ValueTask è un tipo di valore con due campi, mentre Task è un tipo di riferimento con un singolo campo. Quindi utilizzare un ValueTask significa lavorare con più dati poiché una chiamata al metodo restituirebbe due campi di dati invece di uno. Inoltre, se si attende un metodo che restituisce un ValueTask, anche la macchina a stati per quel metodo asincrono sarà più grande, perché dovrebbe ospitare una struttura che contiene due campi al posto di un singolo riferimento nel caso di un'attività.

Inoltre, se il consumatore di un metodo asincrono utilizza Task.WhenAll o Task.WhenAny, l'utilizzo di ValueTask come tipo restituito in un metodo asincrono potrebbe diventare costoso. Questo perché è necessario convertire ValueTask in Task utilizzando il metodo AsTask, che comporterebbe un'allocazione che potrebbe essere facilmente evitata se fosse stata utilizzata innanzitutto un'attività memorizzata nella cache.

Ecco la regola empirica. Utilizzare Task quando si dispone di una parte di codice che sarà sempre asincrona, ovvero quando l'operazione non verrà completata immediatamente. Approfitta di ValueTask quando il risultato di un'operazione asincrona è già disponibile o quando hai già un risultato memorizzato nella cache. In ogni caso, è necessario eseguire l'analisi delle prestazioni necessaria prima di considerare ValueTask.

Come fare di più in C #:

  • Come usare l'immutabilità in C
  • Come usare const, readonly e static in C #
  • Come usare le annotazioni dei dati in C #
  • Come lavorare con i GUID in C # 8
  • Quando usare una classe astratta e un'interfaccia in C #
  • Come lavorare con AutoMapper in C #
  • Come usare le espressioni lambda in C #
  • Come lavorare con i delegati Action, Func e Predicate in C #
  • Come lavorare con i delegati in C #
  • Come implementare un semplice logger in C #
  • Come lavorare con gli attributi in C #
  • Come lavorare con log4net in C #
  • Come implementare il modello di progettazione del repository in C #
  • Come lavorare con la riflessione in C #
  • Come lavorare con Filesystemwatcher in C #
  • Come eseguire l'inizializzazione pigra in C #
  • Come lavorare con MSMQ in C #
  • Come lavorare con i metodi di estensione in C #
  • Come utilizzare espressioni lambda in C #
  • Quando usare la parola chiave volatile in C #
  • Come utilizzare la parola chiave yield in C #
  • Come implementare il polimorfismo in C #
  • Come creare il proprio pianificatore di attività in C #
  • Come lavorare con RabbitMQ in C #
  • Come lavorare con una tupla in C #
  • Esplorazione di metodi virtuali e astratti in C #
  • Come utilizzare Dapper ORM in C #
  • Come utilizzare il modello di progettazione del peso mosca in C #