Stream-uri asincrone
Începând cu versiunea C# 8.0, în C# au fost adăugate stream-urile asincrone, care simplifică lucrul cu fluxurile de date în mod asincron. Deși asincronitatea în C# există de mult timp, metodele asincrone permiteau până acum obținerea unui singur obiect, atunci când operația asincronă era pregătită să furnizeze rezultatul.
Pentru returnarea mai multor valori în C# pot fi folosiți iteratori, dar aceștia au o natură sincronă, blochează firul apelant și nu pot fi utilizați în context asincron. Stream-urile asincrone evită această problemă, permițând obținerea mai multor valori și returnarea acestora pe măsură ce sunt disponibile în mod asincron.
În esență, un stream asincron reprezintă o metodă care are trei caracteristici:
- Metoda are modificatorul async
- Metoda returnează un obiect IAsyncEnumerable<T>. Interfața IAsyncEnumerable definește metoda GetAsyncEnumerator, care returnează IAsyncEnumerator:
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
T Current { get; }
ValueTask<bool> MoveNextAsync();
}
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
- Metoda conține expresii yield return pentru obținerea secvențială a elementelor din stream-ul asincron
De fapt, stream-ul asincron combină asincronitatea și iteratorii. Să analizăm un exemplu simplu:
await foreach (var number in GetNumbersAsync())
{
Console.WriteLine(number);
}
async IAsyncEnumerable<int> GetNumbersAsync()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
yield return i;
}
}
Metoda GetNumbersAsync() reprezintă de fapt un stream asincron. Această metodă este asincronă. Tipul său returnat este IAsyncEnumerable<int>. Metoda returnează cu ajutorul yield return câte un număr la fiecare 100 de milisecunde. Astfel, metoda trebuie să returneze 10 numere de la 0 la 9 cu o pauză de 100 de milisecunde între ele.
Pentru a obține date din stream în metoda Main, se folosește ciclul foreach:
await foreach (var number in GetNumbersAsync())
Este important de menționat că ciclul este precedat de operatorul await. Astfel, de fiecare dată când stream-ul asincron va returna un nou număr, ciclul îl va prelua și îl va afișa pe consolă.
Unde pot fi utilizate stream-urile asincrone? Stream-urile asincrone pot fi utilizate pentru obținerea datelor dintr-un depozit extern. De exemplu, să presupunem că avem următoarea clasă a unui depozit:
class Repository
{
string[] data = { "Tom", "Sam", "Kate", "Alice", "Bob" };
public async IAsyncEnumerable<string> GetDataAsync()
{
for (int i = 0; i < data.Length; i++)
{
Console.WriteLine($"Obținem elementul {i + 1}");
await Task.Delay(500);
yield return data[i];
}
}
}
Pentru simplificarea exemplului, datele sunt reprezentate aici sub forma unui simplu array intern de șiruri de caractere. Pentru a imita întârzierea în obținerea datelor, se folosește metoda Task.Delay.
Să obținem aceste date în program:
Repository repo = new Repository();
IAsyncEnumerable<string> data = repo.GetDataAsync();
await foreach (var name in data)
{
Console.WriteLine(name);
}
class Repository
{
string[] data = { "Tom", "Sam", "Kate", "Alice", "Bob" };
public async IAsyncEnumerable<string> GetDataAsync()
{
for (int i = 0; i < data.Length; i++)
{
Console.WriteLine($"Obținem elementul {i + 1}");
await Task.Delay(500);
yield return data[i];
}
}
}
În acest exemplu, se creează o instanță a clasei Repository și se obțin datele asincron folosind metoda GetDataAsync(). Folosind await foreach, se iterează prin elementele returnate de stream-ul asincron și se afișează pe consolă.