Capita spesso di sentir parlare di qualcosa di nuovo in .NET. Generalmente si tratta di tecniche di programmazione o di oggetti del Framework che non conoscevo, ma raramente escono fuori keyword del linguaggio nuove. Bene, il mio responsabile ha citato "yield". YIELD?! che diavolo è?! rapida ricerca per la traduzione e salta fuori che significa "Precedenza".... merita un'approfondimento!
Iniziamo a capire il funzionamento del comando con un esempio. Abbiamo un programma che deve recuperare ed elaborare un tot di dati, la richiesta a causa di complicatissime query impiega 1 secondo per ogni elemento, l'elaborazione a causa di altrettanto complicati calcoli iper-matriciali impiega 1,5 secondi. Supponiamo di voler elaborare 10 risultati, tempi previsti (1*10)+(1,5*10) = 25 secondi.
L'approccio che vogliamo utilizzare è quello di recuperare tutte le informzazioni, inserirle in una collection e poi ciclarci sopra per ogni elaborazione.
public static IEnumerable GetEnum()
{
int i = 0;
List<int> Enum = new List<int>();
while (i < 10)
{
Thread.Sleep(1000); //Query complicatissima
Enum.Add(i++);
}
return Enum;
}
public static void Print(object x)
{
Thread.Sleep(1500); //Calcoli iper-matriciali
decimal Elapsed = ((decimal)(DateTime.Now.Ticks - Start.Ticks)) / 10000000;
Console.WriteLine(x + ") " + Elapsed.ToString());
}
static void Main(string[] args)
{
Start =
DateTime.Now;
IEnumerable Enumeratore = GetEnum(); //Recupero tutte le informazioni
foreach (int x in Enumeratore)
{
Print(x);//Eseguo la computazione
}
Console.ReadLine();
}
Usando questo codice però avrò da aspettare 10 secondi prima che il programma possa stampare a video qualche infoirmazione, e qui entra in campo il comando yeld.
Sostituiamo la funzione precedente con questa
public static IEnumerable GetEnum()
{
int i = 0;
while (i < 10)
{
Thread.Sleep(1000);
yield return i++;
}
yield break;
}
Come possiamo vedere ogni elaborazione impiega sempre 1 secondo.
Come cambia il funzionamento? Ogni volta che viene richiesto foreach (int x in Enumeratore) viene eseguito un ciclo del while fino al primo yield return i++;. il comando yield return restituisce il valore e "congela" l'esecuzione della funzione fino al prossimo foreach. Quando non ci sono più valori yield break; interrompe il flusso della funzione.
Applicando al programma precedente la nuova get enum avremo sempre un'elaborazione di 25 secondi, ma compare a video un'informazione ogni 2.5 sec (1 sec per la query e 1.5 per la computazione), Rispetto a prima in cui rimaneva bloccata l'applicazione per 10 secondi per poi sfornare un risultato ogni 1.5.
Da notare che non è stata fatta alcuna modifica al main, quindi un'eventuale migrazione di codice preesistente non comporta problemi.
Arrivati a queto punto mi è saltato in mente di utilizzare in coppia con yeld il multithred per il notro consumer di dati.
Stavolta ad essere modificato è il main
static
void Main(string[] args)
{
Start =
DateTime.Now;
IEnumerable Enumeratore = GetEnum(); //Mi aggancio alla funzione per recuperare i valori.
foreach (int x in Enumeratore)
{
Thread Stampa = new Thread(new ParameterizedThreadStart(Print));
Stampa.Start(x);
}
Console.ReadLine();
}
Adesso stiamo chiamando il metodo print in un processo separato (magari è un elaborazione remota).
Come nel metodo precedente il primo risultato lo otterrò dopo 2.5 secondi, ma tutti gli altri ogni 1: totale elaborazione 11.5 secondi contro 25.
Il trucco ovviamente stà nell'elaborazione parallela del multithread.
Ovviamente in questo esempio ero avvantaggiato dal fatto che i tempi sono fissi e che la seconda elaborazione è più lunga della prima, altriemnti avrei dovuto costruirmi dei buffer per gestire i risultati, ma essendo a titolo esemplificativo non era il caso.
Spero che l'articolo vi sia interessato: per domande, chiarimenti e soprattutto correzioni scrivetemi :)
altre informazioni le potete trovare sul sito msdn
Wamba
posted @ domenica 29 aprile 2007 12:42