A Task Parallel Library lehetÅ‘séget adott arra, hogy párhuzamosÃtsuk egy kollekció bejárását és feldolgozását. Az implementáció problémája az, hogy a feldolgozandó kollekció párhuzamosÃtásának van egy elÅ‘feltétele: a kollekció elemeinek a memóriában kell lenniük.
Tételezzük fel, hogy webrÅ‘l töltünk le adatokat, amiken műveleteket kell végeznünk. Ebben az esetben a legcélszerűbb az, ha minden egyes letöltés egy külön Task, Ãgy a letöltések párhuzamosan tudnak futni. Azonban az adatok feldolgozását csak akkor tudjuk megkezdeni, ha a kollekció összes eleme letöltÅ‘dött. Ez a mai párhuzamos adatfeldolgozás követelményeinek nem igen felel meg, mivel az adatok áramlása nem folyamatos.
A megoldást a C# 8.0-ban bemutatott, megtévesztő nevű Async Streams nyelvi szolgáltatás hidalja át. A név olyan szempontból megtévesztő, hogy első olvasatra hajlamos az ember a fájlkezelésnél bemutatott Stream osztályra asszociálni.
C# 8.0 újdonsága az IAsyncEnumerable<T> tÃpus, ami aszinkron végrehajtást enged. Használatára nézzünk egy példát:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace PeldaAsyncStream
{
class Program
{
//IAsyncEnumerable csak async módon deklarálható
public static async IAsyncEnumerable<int> SzamSor()
{
for (int i = 0; i <= 20; i++)
{
await Task.Delay(200);
yield return i;
}
}
static async Task Main(string[] args)
{
//Aszinkron bejárás
await foreach (var szam in SzamSor())
{
Console.Write("{0}, ", szam);
}
Console.ReadKey();
}
}
}
A program kimenete
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
A program kimenetében statikusan szemlélve nincs semmi érdekes, azonban ha futtatjuk, akkor azt tapasztaljuk, hogy az elsÅ‘ elem 200ms után jelenik meg a képernyÅ‘n, majd 200ms mulva a következÅ‘ és Ãgy tovább. Amint végez a Task.Delay(200); hÃvás és a yield utasÃtás vérehajtódik, az elem feldolgozása azonnal megkezdÅ‘dik, Ãgy folyamatos tud lenni az adatáramlás, még akkor is, ha egy elem feldolgozása hosszabb idÅ‘t vesz igénybe.
Az IAsyncEnumerable tÃpus érdekesége, hogy csak az async kulcsszóval deklarálható és elemeket csak yield return segÃtségével tud visszaadni, vagyis kollekciót nem tudunk visszaadni. Ez annak köszönhetÅ‘, hogy az IAsyncEnumerable interfész nem implementálja az IEnumerable<T> interfészt.
Ez a technika is remekül használható termelÅ‘/fogyasztó jellegű problémákra, ahol a fogyasztó lazy módon húzza a termelÅ‘tÅ‘l az újabb és újabb elemeket. Ennek a megoldásnak a termelÅ‘ által tolt pipeline alternatÃvája lehet a Reactive Programming, de annak tárgyalása kÃvül esik jelen könyv keretein.