Returnarea rezultatului din metoda asincronă
Ca tip de returnare în metoda asincronă trebuie să se folosească tipurile void, Task, Task<T> sau ValueTask<T>
Void
La utilizarea cuvântului cheie void, metoda asincronă nu returnează nimic:
PrintAsync("Hello World");
PrintAsync("Hello FDC.COM");
Console.WriteLine("Main End");
await Task.Delay(3000); // așteptăm finalizarea sarcinilor
// definirea metodei asincrone
async void PrintAsync(string message)
{
await Task.Delay(1000); // imitația unei lucrări de lungă durată
Console.WriteLine(message);
}
Totuși, metodele asincrone void ar trebui evitate și utilizate doar acolo unde astfel de metode reprezintă singura modalitate posibilă de a defini o metodă asincronă. În primul rând, nu putem aplica operatorul await acestor metode.
De asemenea, pentru că excepțiile din astfel de metode sunt dificil de gestionat, deoarece nu pot fi capturate în afara metodei. În plus, astfel de metode void sunt greu de testat.
Totuși, există situații în care astfel de metode sunt inevitabile - de exemplu, la gestionarea evenimentelor:
Account account = new Account();
account.Added += PrintAsync;
account.Put(500);
await Task.Delay(2000); // așteptăm finalizarea
// definirea metodei asincrone
async void PrintAsync(object? obj, string message)
{
await Task.Delay(1000); // imitația unei lucrări de lungă durată
Console.WriteLine(message);
}
class Account
{
int sum = 0;
public event EventHandler<string>? Added;
public void Put(int sum)
{
this.sum += sum;
Added?.Invoke(this, $"În cont au fost depuși {sum} $");
}
}
În acest caz, evenimentul Added în clasa Account reprezintă un delegat EventHandler, care are tipul void. În consecință, pentru acest eveniment se poate defini doar o metodă handler cu tipul void.
Task
Returnarea unui obiect de tip Task:
await PrintAsync("Hello FDC.com");
// definirea metodei asincrone
async Task PrintAsync(string message)
{
await Task.Delay(1000); // imitația unei lucrări de lungă durată
Console.WriteLine(message);
}
Aici, formal, metoda PrintAsync nu folosește operatorul return pentru a returna un rezultat. Totuși, dacă în metoda asincronă se execută o operație asincronă în expresia await, putem returna un obiect Task din metodă.
Pentru a aștepta finalizarea unei sarcini asincrone se poate folosi operatorul await. De altfel, nu este obligatoriu să-l folosim direct la apelul sarcinii. Îl putem aplica doar acolo unde trebuie să obținem garantat rezultatul sarcinii sau să ne asigurăm că sarcina este finalizată.
var task = PrintAsync("Hello FDC.com"); // sarcina începe să se execute
Console.WriteLine("Main Works");
await task; // așteptăm finalizarea sarcinii
// definirea metodei asincrone
async Task PrintAsync(string message)
{
await Task.Delay(0);
Console.WriteLine(message);
}
Task
Metoda poate returna o anumită valoare. Atunci valoarea returnată este înfășurată într-un obiect Task, iar tipul returnat este Task<T>:
int n1 = await SquareAsync(5);
int n2 = await SquareAsync(6);
Console.WriteLine($"n1={n1} n2={n2}"); // n1=25 n2=36
async Task<int> SquareAsync(int n)
{
await Task.Delay(0);
return n * n;
}
În acest caz, metoda Square returnează o valoare de tip int - pătratul numărului. Prin urmare, tipul returnat este Task<int>.
Pentru a obține rezultatul metodei asincrone aplicăm operatorul await la apelul SquareAsync:
int n1 = await SquareAsync(5);
În mod similar, putem obține date de alte tipuri:
Person person = await GetPersonAsync("Tom");
Console.WriteLine(person.Name); // Tom
// definirea metodei asincrone
async Task<Person> GetPersonAsync(string name)
{
await Task.Delay(0);
return new Person(name);
}
record class Person(string Name);
Din nou, obținerea rezultatelor directe ale sarcinii asincrone poate fi amânată până în momentul în care acestea sunt necesare:
var square5 = SquareAsync(5);
var square6 = SquareAsync(6);
Console.WriteLine("Alte acțiuni în metoda Main");
int n1 = await square5;
int n2 = await square6;
Console.WriteLine($"n1={n1} n2={n2}"); // n1=25 n2=36
async Task<int> SquareAsync(int n)
{
await Task.Delay(0);
var result = n * n;
Console.WriteLine($"Pătratul numărului {n} este {result}");
return result;
}
Exemplu de lucru al programului (afișajul nu este determinist):
Pătratul numărului 5 este 25
Pătratul numărului 6 este 36
Alte acțiuni în metoda Main
n1=25 n2=36
ValueTask
Utilizarea tipului ValueTask<T> este în mare parte similară cu utilizarea Task<T>, cu unele diferențe în ceea ce privește gestionarea memoriei, deoarece ValueTask este o structură care conține un număr mai mare de câmpuri. De aceea, utilizarea ValueTask în loc de Task duce la copierea unui număr mai mare de date și, în consecință, generează unele costuri suplimentare.
Avantajul ValueTask față de Task este că acest tip permite evitarea alocărilor suplimentare de memorie în heap. De exemplu, uneori este necesar să returnăm sincron o anumită valoare. Să luăm următorul exemplu:
var result = await AddAsync(4, 5);
Console.WriteLine(result);
Task<int> AddAsync(int a, int b)
{
return Task.FromResult(a + b);
}
Aici metoda AddAsync returnează sincron o anumită valoare - în acest caz suma a două numere. Cu ajutorul metodei statice Task.FromResult putem returna sincron o anumită valoare.
Totuși, utilizarea tipului Task va duce la alocarea unei sarcini suplimentare cu alocările de memorie aferente în heap. ValueTask rezolvă această problemă:
var result = await AddAsync(4, 5);
Console.WriteLine(result);
ValueTask<int> AddAsync(int a, int b)
{
return new ValueTask<int>(a + b);
}
În acest caz, un obiect Task suplimentar nu va fi creat și, în consecință, memoria suplimentară nu va fi alocată. De aceea, ValueTask este de obicei utilizat atunci când rezultatul operației asincrone este deja disponibil.
Dacă este necesar, se poate transforma un ValueTask într-un obiect Task cu ajutorul metodei AsTask():
var getMessage = GetMessageAsync();
string message = await getMessage.AsTask();
Console.WriteLine(message); // Hello
async ValueTask<string> GetMessageAsync()
{
await Task.Delay(0);
return "Hello";
}