<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>PLINQ</title>
        <link>http://blogs.ugidotnet.org/PietroLibroBlog/category/PLINQ.aspx</link>
        <description>PLINQ</description>
        <language>it-IT</language>
        <copyright>Pietro Libro</copyright>
        <managingEditor>pietro.libro@libero.it</managingEditor>
        <generator>Subtext Version 2.6.0.0</generator>
        <item>
            <title>PLINQ: Task e Future&amp;lt;T&amp;gt;</title>
            <link>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2009/01/12/plinq-task-e-futurelttgt.aspx</link>
            <description>La  classe System.Threading.Task.Task è una classe che rappresenta un'operazione asincrona, o meglio, un wrapper di un delegate contenente tutte le informazioni necessarie per conoscere lo stato di avanzamento di un’operazione, ad esempio se è terminata perchè completata o interrotta dall’utente, mediante le proprietà IsCompleted e IsCanceled o per interrompere l'esecuzione dell'operazione, chiamando l’apposito metodo Cancel. Essendo la classe Task priva di costruttori pubblici, per crearne un'istanza è necessario utilizzare uno degli overload del metodo statico Create. Per attendere il termine di un'operazione asincrona, un'istanza della classe Task mette a disposizione il metodo Wait (o meglio tre overload): Wait() senza parametri che attende la conclusione delle operazioni, Wait (TimeSpan span) che accetta un oggetto TimeSpan per indicare il tempo di attesa in millisecondi per il completamento delle operazioni ed infine Wait (int milliseonds) che accetta il numero di millisecondi per cui si vuole attendere la conclusione delle operazioni. Possiamo vedere immediatamente la classe Task in azione con un piccolo esempio. Supponiamo di avere la seguente funzione il cui scopo è creare un file di testo contenente un certo numero di righe, secondo il valore degli argomenti passati:   1 private static void WriteFile(IsolatedStorageFile storage,  int start, int count)
 2 {
 3     string fileName = string.Format("start_{0}.txt", start);            
 4 
 5     lock (storage)
 6     {
 7         using (IsolatedStorageFileStream sw = new IsolatedStorageFileStream(fileName, System.IO.FileMode.OpenOrCreate, storage))
 8         {
 9             using (System.IO.StreamWriter swriter = new System.IO.StreamWriter(sw))
10             {
11                 for (int i = start; i &amp;lt; start + count; i++)
12                     swriter.WriteLine("Item#{0}", i);
13             }
14         }
15     }            
16 }

Supponiamo di voler scrivere 5 file ed attendere per il completamento delle operazioni, possiamo scrivere qualcosa del tipo:    1 Task tFile1 = Task.Create(delegate { WriteFile(storage, 1, 10); }); 
2 Task tFile2 = Task.Create(delegate { WriteFile(storage, 11, 10); }); 
3 Task tFile3 = Task.Create(delegate { WriteFile(storage, 21, 10); }); 
4 Task tFile4 = Task.Create(delegate { WriteFile(storage, 31, 10); }); 
5 Task tFile5 = Task.Create(delegate { WriteFile(storage, 41, 10); }); 
6 Console.WriteLine("Writing..."); 
7 //Attende il completamento di tutti i task, 
8 Task.WaitAll(new Task[] { tFile1, tFile2, tFile3, tFile4, tFile5 });



  Invece di richiamare il metodo Wait (o uno dei suoi overload per ogni istanza di Task, possiamo richiamare il metodo statico WaitAll (anch'esso definito con tre  overload) che accetta come primo parametro un vettore di oggetti  Task (o da essi derivati), per i quali attendere il completamento delle operazioni. Fin qui tutto bene, ma il metodo WriteFile, esegue l'operazione senza ritornare nessun valore. Supponiamo allora di voler eseguire la scansione "parallela" di un certo numero di directory su disco per ottenere il numero di file di testo (*.txt) contenuti nelle directory. Una funzione molto semplice potrebbe essere di questo tipo: 

1 public static int ScanDirectory( string path, string searchPattern) 
2 { 
3     return System.IO.Directory.GetFiles(path, searchPattern).Count(); 
4 }

In questo caso, a meno di creare una classe personalizzata che derivi da Task, possiamo utilizzare la classe Future&amp;lt;T&amp;gt;, che deriva da Task, e che permette il recupero di un valore di ritorno da un'operazione asincrona. Semplicemente possiamo scrivere:

1 Future&amp;lt;int&amp;gt; tSearch1 = Future.Create(() =&amp;gt; ScanDirectory(@"C:\", "*.txt")); 
2 Future&amp;lt;int&amp;gt; tSearch2 = Future.Create(() =&amp;gt; ScanDirectory(@"C:\DIR1", "*.txt")); 
3 Future&amp;lt;int&amp;gt; tSearch3 = Future.Create(() =&amp;gt; ScanDirectory(@"C:\DIR2", "*.txt")); 
4 Task.WaitAll(new Future&amp;lt;int&amp;gt;[] { tSearch1, tSearch2, tSearch3 });

Per quanto detto precedentemente, anche in questo caso è possibile utilizzare il metodo statico WaitAll per attendere il completamento delle operazioni. Per recuperare il numero di file di testo trovati nelle directory, accediamo alla proprietà Value di ogni istanza di Future&amp;lt;int&amp;gt;:

1 int filesCount = tSearch1.Value + tSearch2.Value + tSearch3.Value; 
2 Console.WriteLine("Files Count:" + filesCount); 

Ci sono delle piccole osservazioni da fare. Supponiamo di modificare la funzione ScanDirectory in questo modo:   

  1 public static int ScanDirectory( string path, string searchPattern) 
2 { 
3     System.Threading.Thread.Sleep(3000); 
4     return System.IO.Directory.GetFiles(path, searchPattern).Count(); 
5 }


e di eseguire il codice seguente:


  1 Future&amp;lt;int&amp;gt; tSearch1 = Future.Create(() =&amp;gt; ScanDirectory(@"C:\Lavoro", "*.txt"));             
2 tSearch1.Wait(1000); 
3 int filesCount = 0; 
4 filesCount = tSearch1.Value; 
5 Console.WriteLine("Files Count:" + filesCount); 


Supponendo che la directory “C:\Lavoro” contenga 4 file di testo, ci si potrebbe aspettare che il valore visualizzato sulla console sia zero (dato che Wait attende per solo un secondo), invece, verrà visualizzato il valore corretto, dato che l’accesso alla proprietà Value  sì comporta come Wait senza parametri. Si potrebbe allora pensare di utilizzare  il metodo Cancel(), subito dopo il Wait, ma verrebbe sollevata un'eccezione  quando si tenta di recuperare  il valore da Value:  


  1 tSearch1.Wait(1000); 
2 tSearch1.Cancel(); 


Una possibile soluzione è quella di sfruttare le proprietà IsCanceled (o in casi diversi IsCompleted), in questo modo:   

1 if(!tSearch1.IsCanceled ) filesCount = tSearch1.Value;

La semplificazione introdotta da queste classi per la gestione delle operazioni asincrone è veramente impressionante. &lt;img src="http://blogs.ugidotnet.org/PietroLibroBlog/aggbug/95177.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Pietro Libro</dc:creator>
            <guid>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2009/01/12/plinq-task-e-futurelttgt.aspx</guid>
            <pubDate>Mon, 12 Jan 2009 19:29:00 GMT</pubDate>
            <comments>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2009/01/12/plinq-task-e-futurelttgt.aspx#feedback</comments>
            <slash:comments>2</slash:comments>
            <wfw:commentRss>http://blogs.ugidotnet.org/PietroLibroBlog/comments/commentRss/95177.aspx</wfw:commentRss>
            <trackback:ping>http://blogs.ugidotnet.org/PietroLibroBlog/services/trackbacks/95177.aspx</trackback:ping>
        </item>
        <item>
            <title>Microsoft Parallel Extensions e Download Paralleli</title>
            <link>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/12/30/microsoft-parallel-extensions-e-download-paralleli.aspx</link>
            <description>Un piccolo problema risolvibile (volendo) utilizzando  le Microsoft Parallel Extensions per il .Net Framework. Supponiamo di avere un testo HTML contenente un certo numero di collegamenti a file di cui si vuole effettuare il download. Per ottenere una lista dei link, possiamo ad esempio utilizzare la classe Regex ed un'opportuna Regular Expression:     Regex reg = new Regex(regular_expression_pattern);
MatchCollection matches = reg.Matches(pageContent);


Ottenuta la collezione matches , possiamo seguire diverse strade per eseguire il download dei file, utilizzando istanze di System.Net.WebClient.

1) Eseguiamo un ciclo for e sequenzialmente eseguiamo il download dei file;

2) Scriviamo del codice per l'esecuzione su più thread utilizzando unThreadPool

3) Utilizziamo le Microsoft Parallel Extensions per il .Net Framework 3.5 (CTP 2008 al momento), scaricabili qui.

Sinceramente qualche settimana fa avrei utilizzato il secondo approccio, ma dato che le Parallel Extensions forniscono classi e metodi che semplificano la parallelizzazione del codice senza entrare nel dettaglio di thread e pool di thread, mi è sembrata una buona occasione per sperimentarle. Dovendo eseguire un ciclo sugli oggetti System.Text.RegularExpressions.Match presenti nella collezione matches, possiamo utilizzare  il metodo Parallel.ForEach, il quale implementa l'esecuzione parallela dello statement foreach. Dovendo condividere alcune  informazioni con i vari thread, come la directory dove depositare il file una volta scaricato e l'indirizzo base del sito (come ad esempio www.contoso.com) necessario alla ricostruzione completa dell'url da cui prelevare i file, invece di seguire strane alchimie, possiamo utilizzare un oggetto ParallelState o meglio ParallelState&amp;lt;DownloadStartInfo&amp;gt;, dove la classe DownloadStartInfo è così definita:


   1 private class DownloadStartInfo
 2 {
 3     private string _baseUrl = "";
 4     private string _directoryTarget = "";
 5 
 6     public DownloadStartInfo(string baseUrl, string directoryTarget)
 7     {
 8         _baseUrl = baseUrl;
 9         _directoryTarget = directoryTarget;
10     }
11 
12     public string BaseUrl
13     {
14         get { return _baseUrl; }
15         set { _baseUrl = value; }
16     }
17 
18     public string DirectoryTarget
19     {
20         get { return _directoryTarget; }
21         set { _directoryTarget = value; }
22     }
23 }


L'istanza di oggetto DownloadStartInfo sarà memorizzata all'interno della proprietà ParallelState.ThreadLocalState. Non resta quindi che scrivere il codice della funzione utilizzata per eseguire il Download dei file:


   1         private static void DownloadFile(Match m, int i, ParallelState&amp;lt;DownloadStartInfo&amp;gt; ps)
 2         {
 3 
 4             try
 5             {
 6                 if (!System.IO.Directory.Exists(ps.ThreadLocalState.DirectoryTarget))
 7                     System.IO.Directory.CreateDirectory(ps.ThreadLocalState.DirectoryTarget);
 8 
 9                 string relativeUrl = m.Groups["relativeUrl"].Value;
10                 string fileName = m.Groups["fileName"].Value;
11                 string address = string.Format("{0}{1}/{2}", ps.ThreadLocalState.BaseUrl , relativeUrl, fileName);
12 
13                 System.Net.WebClient client = new System.Net.WebClient();
14 
15                 string fullFilePath = System.IO.Path.Combine(ps.ThreadLocalState.DirectoryTarget, fileName);
16 
17                 Console.WriteLine("\t\tDownloading {0}", fileName);                
18                 client.DownloadFile(address, fullFilePath );
19                 Console.WriteLine("\t\tDownload of {0} completed", fileName);
20 
21                 System.Threading.Thread.Sleep(150);
22             }
23             catch (System.Net.WebException wex)
24             {
25                 Console.WriteLine("Error ==&amp;gt; {0}", wex.Message);
26             }
27             catch (Exception ex)
28             {
29                 Console.WriteLine("Error ==&amp;gt; {0}", ex.Message);
30             }            
31         }


Mettiamo tutto insieme  scrivendo il codice relativo al Parallel.ForEach&amp;lt;&amp;gt;:


  1 IEnumerable&amp;lt;Match&amp;gt; ms = matches.Cast&amp;lt;Match&amp;gt;();
2 
3 Parallel.ForEach&amp;lt;Match, DownloadStartInfo&amp;gt;(ms, 
4     delegate(){return new DownloadStartInfo("http://www.contoso.com", directoryTarget);},
5     delegate(Match m, int i, ParallelState&amp;lt;DownloadStartInfo&amp;gt; ps) { DownloadFile(m, i, ps); });


dove il primo Delegate indica la funzione che serve a generare i dati thread-Local (a cui si accede  utilizzando l'oggetto ParallelState&amp;lt;...&amp;gt;) utilizzati localmente dai vari thread, mentre il secondo Delegate specifica il codice  da richiamare ad ogni iterazione del Parallel.ForEach&amp;lt;...,..&amp;gt;. Oltre al metodo Parallel.ForEach, possiamo trovare anche il metodo Parallel.For, questi sono molto simili tra loro, ma entrambi richiedono che l'operazione eseguita da cisascun ciclo sia indipendente dagli altri. Piccola osservazione, per eseguire il codice, è necessarion aggiungere un riferimento all'assembly System.Threading.dll

 

Technorati Tag: Parallel Extensions,ParallelState&lt;img src="http://blogs.ugidotnet.org/PietroLibroBlog/aggbug/95045.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Pietro Libro</dc:creator>
            <guid>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/12/30/microsoft-parallel-extensions-e-download-paralleli.aspx</guid>
            <pubDate>Tue, 30 Dec 2008 17:59:58 GMT</pubDate>
            <comments>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/12/30/microsoft-parallel-extensions-e-download-paralleli.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.ugidotnet.org/PietroLibroBlog/comments/commentRss/95045.aspx</wfw:commentRss>
            <trackback:ping>http://blogs.ugidotnet.org/PietroLibroBlog/services/trackbacks/95045.aspx</trackback:ping>
        </item>
        <item>
            <title>Query Data with Parallel LINQ</title>
            <link>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/12/16/query-data-with-parallel-linq.aspx</link>
            <description>Ho trovato molto interessante questo post sul blog di Charlie Calvert sull'uso di PLINQ (o meglio Microsoft Parallel Extensions) per la scrittura di codice che tragga vantaggio da sistemi con architetture multiprocessore. Magari può interessare anche ad altri...  Technorati Tag: PLINQ&lt;img src="http://blogs.ugidotnet.org/PietroLibroBlog/aggbug/94959.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Pietro Libro</dc:creator>
            <guid>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/12/16/query-data-with-parallel-linq.aspx</guid>
            <pubDate>Tue, 16 Dec 2008 19:27:14 GMT</pubDate>
            <comments>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/12/16/query-data-with-parallel-linq.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.ugidotnet.org/PietroLibroBlog/comments/commentRss/94959.aspx</wfw:commentRss>
            <trackback:ping>http://blogs.ugidotnet.org/PietroLibroBlog/services/trackbacks/94959.aspx</trackback:ping>
        </item>
    </channel>
</rss>