<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://msmvps.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Angel "Java" Lopez : C Sharp, Programación</title><link>http://msmvps.com/blogs/lopez/archive/tags/C+Sharp/Programaci_F300_n/default.aspx</link><description>Tags: C Sharp, Programación</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP2 (Build: 40407.4157)</generator><item><title>Algoritmos Genéticos usando GoRoutines y Channels en C#</title><link>http://msmvps.com/blogs/lopez/archive/2010/02/08/genetic_2D00_algorithms_2D00_using_2D00_goroutines_2D00_and_2D00_channels_2D00_in_2D00_c.aspx</link><pubDate>Mon, 08 Feb 2010 07:56:36 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1755900</guid><dc:creator>lopez</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/lopez/rsscomments.aspx?PostID=1755900</wfw:commentRss><comments>http://msmvps.com/blogs/lopez/archive/2010/02/08/genetic_2D00_algorithms_2D00_using_2D00_goroutines_2D00_and_2D00_channels_2D00_in_2D00_c.aspx#comments</comments><description>&lt;p&gt;Hace más de un año, escribí:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://msmvps.com/blogs/lopez/archive/2008/04/13/algoritmos-gen-233-ticos-con-ajagents-y-concurrency-and-coordination-runtime-ccr.aspx" target="_blank"&gt;Algoritmos Genéticos con AjAgents y Concurrency and Coordination Runtime (CCR)&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://ajlopez.wordpress.com/2008/04/10/genetic-algorithms-with-ajagents-and-concurrency-and-coordination-runtime-ccr/" target="_blank"&gt;Genetic Algorithms with AjAgents and Concurrency and Coordination Runtime (CCR)&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;explorando la concurrencia en la implementación de una solución al &lt;a href="http://en.wikipedia.org/wiki/Travelling_salesman_problem" target="_blank"&gt;Travelling Salesman Problem&lt;/a&gt; usando un algoritmo genético. Al final del año pasado, escribí un ejemplo, usando lo que ya había implementado en C#:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://msmvps.com/blogs/lopez/archive/2010/01/03/goroutines_2D00_and_2D00_channels_2D00_in_2D00_c.aspx" target="_blank"&gt;GoRoutines y Canales en C#&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://ajlopez.wordpress.com/2010/01/02/goroutines-and-channels-in-c/" target="_blank"&gt;GoRoutines and Channels in C#&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;El código de este experimento está en el proyecto AjConcurr.Tsp en el repositorio:&lt;/p&gt;  &lt;p&gt;&lt;a title="http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjConcurr" href="http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjConcurr"&gt;http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjConcurr&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Este es el resultado (no es una gran interfaz… ;-):&lt;/p&gt;  &lt;p&gt;&lt;img src="http://www.ajlopez.com/images/articles2/ajconcurr02.png" alt="" /&gt; &lt;/p&gt;  &lt;p&gt;TSP no es un buen problema para resolver usando algoritmos genéticos: el resultado depende de la mejora de la población de soluciones tentativas después de cada iteración. En esta implementación uso un algoritmo de mutación (no estoy usando crossover) que intenta generar mejores individuos (en general, un algoritmo de mutación debería solamente cambiar al individuo, dejando la emergencia de mejoras al algoritmo genético mismo). El código está escrito en un gran método, que se lanza desde el botón Run. Es código sólo para demostración de la idea.&lt;/p&gt;  &lt;p&gt;Al comienzo de ese método, se definen los canales a usar:&lt;/p&gt;  &lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;            Channel genomas = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Channel();
            Channel evaluated = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Channel();
            Channel bestsofar = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Channel();
            Channel mutator = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Channel();
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Recordemos: podemos poner un valor (un objeto cualquiera) en un channel, y en otro thread, podemos obtener el valor. Si colocamos un valor en un channel sin que haya otro thread que está esperando ese valor, nuestro thread se bloquea. Si tratamos de leer el próximo valor de un channel, y no hay tal valor, nuestro thread se bloquea. De esta forma, productores y consumidores de valores se van sincronizando.&lt;/p&gt;

&lt;p&gt;El canal Evaluated recibe individuos (soluciones del problema) y los collecciona, formando una nueva población. El mejor individuo es enviado al canal bestsofar, que mantiene cuál es el mejor individuo hallado hasta el momento en el proceso. El canal genomas recibe un individuo, evalúa la solución que representa, y lo reenvía al canal evaluated. El canal mutator recibe un genoma, lo muta y evalúa. El nuevo genoma es entonces reenvíado al canal evaluated.&lt;/p&gt;

&lt;p&gt;De esta forma, varias poblaciones son procesadas en paralelo. Para ser más exacto, no hay el concepto de población, sino que hay&amp;#160; N * M individuos en proceso. Por cada N de esos individuos que llegan al final del proceso, los mejores son seleccionados, y un nuevo grupo de N individuos es inyectado de nuevo en el proceso.&lt;/p&gt;

&lt;p&gt;Veamos el código, compuesto de varias GoRoutinas. Primero, lanzamos una goruotine para generar la poblaciones iniciales:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#008000;"&gt;// Generates the initial populations&lt;/span&gt;
GoRoutines.Go(() =&amp;gt;
{
    Genoma genoma = GenerateGenoma();
    &lt;span style="color:#0000ff;"&gt;for&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; j = 0; j &amp;lt; PopulationSize * 10; j++)
    {
        genomas.Send(&lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.Mutate(genoma));
    }
});
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Se lanza una goroutine para evaluar nuevos genomas:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#008000;"&gt;// Evaluate genomas&lt;/span&gt;
GoRoutines.Go(() =&amp;gt;
{
    &lt;span style="color:#0000ff;"&gt;while&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;true&lt;/span&gt;)
    {
        Genoma genoma = (Genoma)genomas.Receive();
        genoma.&lt;span style="color:#0000ff;"&gt;value&lt;/span&gt; = &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.CalculateValue(genoma);
        evaluated.Send(genoma);
    }
});
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Una goroutine para coleccionar genomas, seleccionarlos, y generar un nuevo grupo, población:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#008000;"&gt;// Collect and select&lt;/span&gt;
GoRoutines.Go(() =&amp;gt;
{
  GenomaComparer comparer = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; GenomaComparer();
    &lt;span style="color:#0000ff;"&gt;while&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;true&lt;/span&gt;)
    {
    List&amp;lt;Genoma&amp;gt; genomalist = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; List&amp;lt;Genoma&amp;gt;();
    &lt;span style="color:#0000ff;"&gt;for&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; k = 0; k &amp;lt; PopulationSize; k++)
    {
      Genoma genoma = (Genoma)evaluated.Receive();
      genomalist.Add(genoma);
    }
    genomalist.Sort(comparer);
    GoRoutines.Go(() =&amp;gt; bestsofar.Send(genomalist[0]));
    &lt;span style="color:#0000ff;"&gt;for&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; k = 0; k &amp;lt; PopulationSize / 5; k++)
      GoRoutines.Go(() =&amp;gt; evaluated.Send(genomalist[k]));
    &lt;span style="color:#0000ff;"&gt;for&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; k = 0; k &amp;lt; PopulationSize / 5; k++)
    {
      GoRoutines.Go(() =&amp;gt; mutator.Send(genomalist[k]));
      GoRoutines.Go(() =&amp;gt; mutator.Send(genomalist[k]));
      GoRoutines.Go(() =&amp;gt; mutator.Send(genomalist[k]));
      GoRoutines.Go(() =&amp;gt; mutator.Send(genomalist[k]));
    }
    }
});
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;A goroutine for receives genomas, improve them, and forwards them to evaluated channel:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#008000;"&gt;// Mutates&lt;/span&gt;
GoRoutines.Go(() =&amp;gt;
{
    Random rnd = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Random();
    &lt;span style="color:#0000ff;"&gt;while&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;true&lt;/span&gt;)
    {
    Genoma genoma = (Genoma)mutator.Receive();
    Genoma newgenoma = &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.Mutate(genoma);
        &lt;span style="color:#0000ff;"&gt;while&lt;/span&gt; (newgenoma.&lt;span style="color:#0000ff;"&gt;value&lt;/span&gt; &amp;gt;= genoma.&lt;span style="color:#0000ff;"&gt;value&lt;/span&gt;)
        {
            &lt;span style="color:#0000ff;"&gt;if&lt;/span&gt; (rnd.Next(3) == 0)
                &lt;span style="color:#0000ff;"&gt;break&lt;/span&gt;;
        newgenoma = &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.Mutate(genoma);
        }
        evaluated.Send(newgenoma);
    }
});
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Una goroutine para recibir y procesar los mejores encontrados hasta el momento:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#008000;"&gt;// Receives and draws the results&lt;/span&gt;
GoRoutines.Go(() =&amp;gt;
{
    Genoma best = &lt;span style="color:#0000ff;"&gt;null&lt;/span&gt;;
    &lt;span style="color:#0000ff;"&gt;while&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;true&lt;/span&gt;)
    {
        Genoma genoma = (Genoma)bestsofar.Receive();
        &lt;span style="color:#0000ff;"&gt;if&lt;/span&gt; (best == &lt;span style="color:#0000ff;"&gt;null&lt;/span&gt; || best.&lt;span style="color:#0000ff;"&gt;value&lt;/span&gt; &amp;gt; genoma.&lt;span style="color:#0000ff;"&gt;value&lt;/span&gt;)
        {
            best = genoma;
            &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.BestGenoma(genoma);
        }
    }
});
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;No puse una operación de crossover. El caso es que el problema y el algoritmo podrían cambiar, pero la idea es explorar la implementación paralela de un algoritmo genético.&lt;/p&gt;

&lt;p&gt;El principal problema que encontré es en el proceso de una población acumulada. En ese paso, la goroutine tiene que enviar varios valores a varios canales. Si alimenta un canal, y ésto la bloquea, los demás canales no recibirán sus valores. Tuve que lanzar varias goroutines para solucionar este problema, cada una alimenta a un canal.&lt;/p&gt;

&lt;p&gt;Próximos pasos: implementar el algoritmo usando Queue Channels como en:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://msmvps.com/blogs/lopez/archive/2010/01/29/queue_2D00_channels_2D00_in_2D00_ajsharp.aspx" target="_blank"&gt;Queue Channels en AjSharp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;e implementar un actor model, colocando cada paso en un agente, no en una goroutine. Llegados a ese punto, podría intentar poner los agentes en distintas máquinas.&lt;/p&gt;

&lt;p&gt;Nos leemos!&lt;/p&gt;

&lt;p&gt;Angel “Java” Lopez 
  &lt;br /&gt;&lt;a href="http://www.ajlopez.com"&gt;http://www.ajlopez.com&lt;/a&gt; 

  &lt;br /&gt;&lt;a href="http://twitter.com/ajlopez"&gt;http://twitter.com/ajlopez&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1755900" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/lopez/archive/tags/.NET/default.aspx">.NET</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/C+Sharp/default.aspx">C Sharp</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/Programaci_F300_n/default.aspx">Programación</category></item><item><title>Refactoreando AjLisp: un intérprete Lisp escrito en C#</title><link>http://msmvps.com/blogs/lopez/archive/2010/01/10/refactoring_2D00_ajlisp_2D00_a_2D00_lisp_2D00_interpreter_2D00_written_2D00_in_2D00_c.aspx</link><pubDate>Sun, 10 Jan 2010 11:50:51 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1750792</guid><dc:creator>lopez</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/lopez/rsscomments.aspx?PostID=1750792</wfw:commentRss><comments>http://msmvps.com/blogs/lopez/archive/2010/01/10/refactoring_2D00_ajlisp_2D00_a_2D00_lisp_2D00_interpreter_2D00_written_2D00_in_2D00_c.aspx#comments</comments><description>&lt;p&gt;En estos días estuve reimplementando el núcleo de mi intérprete Lisp de código abierto &lt;a href="http://msmvps.com/blogs/lopez/archive/tags/AjLisp/default.aspx" target="_blank"&gt;AjLisp&lt;/a&gt;. Había escrito sobre la anterior versión en el 2008:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://ajlopez.wordpress.com/2008/07/30/ajlisp-a-lisp-interpreter-in-net/"&gt;AjLisp: a Lisp interpreter in .NET&lt;/a&gt;    &lt;br /&gt;&lt;a href="http://msmvps.com/blogs/lopez/archive/2008/07/31/ajlisp-un-int-233-rprete-lisp-en-net.aspx" target="_blank"&gt;AjLisp: un intérprete Lisp en .NET&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Es trabajo en progreso. Pueden bajar el código de:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://code.google.com/p/ajlisp/source/browse/#svn/trunk/AjLisp"&gt;http://code.google.com/p/ajlisp/source/browse/#svn/trunk/AjLisp&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;(hay otras dos implementaciones en ese repositorio, en desarrollo: &lt;a href="http://code.google.com/p/ajlisp/source/browse/#svn/trunk/AjScheme" target="_blank"&gt;AjScheme&lt;/a&gt;, un Lisp tipo Scheme, y &lt;a href="http://code.google.com/p/ajlisp/source/browse/#svn/trunk/AjSharpure" target="_blank"&gt;AjSharpure&lt;/a&gt;, que intenta seguir las ideas de &lt;a href="http://clojure.org" target="_blank"&gt;Clojure&lt;/a&gt;). Debería escribir sobre esas implementaciones. Este post es el inicial en una serie sobre AjLisp. Es una corta introducción al estado actual del proyecto.&lt;/p&gt;  &lt;p&gt;El principal cambio en esta nueva versión: el intérprete puede manejar objetos y valores nativos. La alternativa hubiera sido apuntar a esos objetos a través de “wrappers”, alguna clase que implemente SymbolicExpression o similar. Pero elegí manejar directamente objetos nativos. Para eso, cambié las primitivas y clases relacionadas para que recibieran como argumentos objetos en vez de IExpression. Sigue habiendo una interfaz IExpression, defined como:&lt;/p&gt;  &lt;pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;    &lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;interface&lt;/span&gt; IExpression
&lt;/pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;    {
&lt;/pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;        &lt;span style="color:#0000ff;"&gt;object&lt;/span&gt; Evaluate(ValueEnvironment environment);
&lt;/pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;    }
&lt;/pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;El ValueEnvironment (planeo cambiarle el nombre a BindingEnvironment, y derivarlo de alguna interfaz tipo IBindingEnvironment) mantiene una asociación anidada de nombres y valores:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://www.ajlopez.com/images/articles2/ajlisp03.png" alt="" /&gt; &lt;/p&gt;

&lt;p&gt;Hay un interfaz IFunction:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;    &lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;interface&lt;/span&gt; IFunction
&lt;/pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;    {
&lt;/pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;        &lt;span style="color:#0000ff;"&gt;object&lt;/span&gt; Apply(List arguments, ValueEnvironment environment);
&lt;/pre&gt;&lt;pre style="font-size:12px;margin:0em;width:100%;font-family:consolas,&amp;#39;Courier New&amp;#39;,courier,monospace;background-color:#ffffff;"&gt;    }&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;que debería ser implementada por cualquier form expression (la cabeza de una lista a evaluar).&lt;/p&gt;

&lt;p&gt;Las dos clases principales que representan los tipos núcleo del AjLisp son identificadores y listas:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://www.ajlopez.com/images/articles2/ajlisp04.png" alt="" /&gt; &lt;/p&gt;

&lt;p&gt;Todo el proyecto fue armado siguiendo Test-Driven Development, así que no tuve problemas en cambiar la versión anterior: tenía toda una batería de tests que me ayudó en el proceso de refactoring. Este es el estado actual de los test:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://www.ajlopez.com/images/articles2/ajlisp05.png" alt="" /&gt; &lt;/p&gt;

&lt;p&gt;Debería escribir sobre:&lt;/p&gt;

&lt;p&gt;- La implementación de ValueEnvironment
  &lt;br /&gt;- Manejo de objetos nativos 

  &lt;br /&gt;- El tipo List y su evaluación 

  &lt;br /&gt;- Evaluación de Identifier 

  &lt;br /&gt;- Las primitivas implementadas 

  &lt;br /&gt;- El Lexer y el Parser 

  &lt;br /&gt;- Operaciones numéricas 

  &lt;br /&gt;- Números racionales (AjLisp puede manejar pares enteros numerador/denominador)&lt;/p&gt;

&lt;p&gt;Ahora, luego de lo que aprendí desarrollando estos proyectos (AjLisp, AjScheme, AjSharpure), estoy escribiendo un núcleo mínimo &lt;a href="http://code.google.com/p/ajlisp/source/browse/#svn/trunk/AjCoreLisp" target="_blank"&gt;AjCoreLisp&lt;/a&gt;, para mostrar cuáles son las primitivas mínimas para crear un intérprete Lisp. Armado con esa implementación, me gustaría explorar alternativas de compilación, en vez de ser sólo intérprete. Mi primer candidato es Dynamic Language Runtime. Otro podría ser la producción de código C# directo. Sería demasiado encarar esa exploración sobre un intérprete “más grande”. Primero, entonces, estudiaré sobre compilar un intérprete con menos primitivas, más condensado.&lt;/p&gt;

&lt;p&gt;Nos leemos!&lt;/p&gt;

&lt;p&gt;Angel “Java” Lopez 
  &lt;br /&gt;&lt;a href="http://www.ajlopez.com"&gt;http://www.ajlopez.com&lt;/a&gt; 

  &lt;br /&gt;&lt;a href="http://twitter.com/ajlopez"&gt;http://twitter.com/ajlopez&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1750792" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/lopez/archive/tags/.NET/default.aspx">.NET</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/C_26002300_243_3B00_digo+Abierto/default.aspx">C&amp;#243;digo Abierto</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/C+Sharp/default.aspx">C Sharp</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/AjLisp/default.aspx">AjLisp</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/Programaci_F300_n/default.aspx">Programación</category></item><item><title>GoRoutines y Canales en C#</title><link>http://msmvps.com/blogs/lopez/archive/2010/01/03/goroutines_2D00_and_2D00_channels_2D00_in_2D00_c.aspx</link><pubDate>Sun, 03 Jan 2010 10:44:17 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1749362</guid><dc:creator>lopez</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/lopez/rsscomments.aspx?PostID=1749362</wfw:commentRss><comments>http://msmvps.com/blogs/lopez/archive/2010/01/03/goroutines_2D00_and_2D00_channels_2D00_in_2D00_c.aspx#comments</comments><description>&lt;p&gt;En post anteriores:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://msmvps.com/blogs/lopez/archive/2009/12/29/channels_2D00_and_2D00_goroutines_2D00_in_2D00_ajsharp_2D00_part_2D00_1.aspx" target="_blank"&gt;Canales y GoRoutines en AjSharp (Part 1)&lt;/a&gt;    &lt;br /&gt;&lt;a href="http://msmvps.com/blogs/lopez/archive/2009/12/31/channels_2D00_and_2D00_goroutines_2D00_in_2D00_ajsharp_2D00_part_2D00_2.aspx" target="_blank"&gt;Canales y GoRoutines en AjSharp (Part 2)&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;describí la implementación de gorutinas (goroutines, como en el lenguaje Go de Google), y canales en mi intérprete &lt;a href="http://msmvps.com/blogs/lopez/archive/tags/AjSharp/default.aspx" target="_blank"&gt;AjSharp&lt;/a&gt;. Al final del año que pasó, escribí una prueba rápida, implementando los mismos conceptos, pero para ser consumidos esta vez desde C# directamente. Pueden ver el código en&lt;/p&gt;  &lt;p&gt;&lt;a title="http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjConcurr" href="http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjConcurr"&gt;http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjConcurr&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Primero, porté la clase Channel:&lt;/p&gt;  &lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;class&lt;/span&gt; Channel
{
    &lt;span style="color:#0000ff;"&gt;private&lt;/span&gt; AutoResetEvent sethandle = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; AutoResetEvent(&lt;span style="color:#0000ff;"&gt;false&lt;/span&gt;);
    &lt;span style="color:#0000ff;"&gt;private&lt;/span&gt; AutoResetEvent gethandle = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; AutoResetEvent(&lt;span style="color:#0000ff;"&gt;false&lt;/span&gt;);
    &lt;span style="color:#0000ff;"&gt;private&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;object&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;value&lt;/span&gt;;

    &lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;void&lt;/span&gt; Send(&lt;span style="color:#0000ff;"&gt;object&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;value&lt;/span&gt;)
    {
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.gethandle.WaitOne();
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.&lt;span style="color:#0000ff;"&gt;value&lt;/span&gt; = &lt;span style="color:#0000ff;"&gt;value&lt;/span&gt;;
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.sethandle.Set();
    }

    &lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;object&lt;/span&gt; Receive()
    {
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.gethandle.Set();
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.sethandle.WaitOne();
        &lt;span style="color:#0000ff;"&gt;object&lt;/span&gt; result = &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.&lt;span style="color:#0000ff;"&gt;value&lt;/span&gt;;
        &lt;span style="color:#0000ff;"&gt;return&lt;/span&gt; result;
    }
}
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;El código tiene una clase estática GoRoutines, con métodos como:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;static&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;void&lt;/span&gt; Go(Action action)
{
    Thread thread = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Thread(&lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; ParameterizedThreadStart(GoRoutines.RunAction));
    thread.IsBackground = &lt;span style="color:#0000ff;"&gt;true&lt;/span&gt;;
    thread.Start(action);
    &lt;span style="color:#008000;"&gt;//ThreadPool.QueueUserWorkItem(new WaitCallback(RunAction), action);&lt;/span&gt;
}

&lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;static&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;void&lt;/span&gt; Go(ITask task)
{
    Thread thread = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Thread(&lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; ParameterizedThreadStart(GoRoutines.RunTask));
    thread.IsBackground = &lt;span style="color:#0000ff;"&gt;true&lt;/span&gt;;
    thread.Start(task);
    &lt;span style="color:#008000;"&gt;//ThreadPool.QueueUserWorkItem(new WaitCallback(RunTask), task);&lt;/span&gt;
}
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Pueden lanzar una Action (lo que es en el framework delegate System.Action&amp;lt;&amp;gt;), en un nuevo thread (traté de hacerlo poniendo la tarea en la cola de ThreadPool, pero, no sé por qué, el rendimiento pasó a ser muy bajo; mi intento quedó en comentarios del código de arriba).&lt;/p&gt;

&lt;p&gt;Cuando una acción recibe parámetros, se encapsulan los dos en una Task&amp;lt;&amp;gt;, como esta que recibe dos parámetros:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;class&lt;/span&gt; Task&amp;lt;T1, T2&amp;gt; : ITask
{
    &lt;span style="color:#0000ff;"&gt;private&lt;/span&gt; Action&amp;lt;T1, T2&amp;gt; action;
    &lt;span style="color:#0000ff;"&gt;private&lt;/span&gt; T1 parameter1;
    &lt;span style="color:#0000ff;"&gt;private&lt;/span&gt; T2 parameter2;

    &lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; Task(Action&amp;lt;T1, T2&amp;gt; action, T1 parameter1, T2 parameter2)
    {
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.action = action;
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.parameter1 = parameter1;
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.parameter2 = parameter2;
    }

    &lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;void&lt;/span&gt; Run()
    {
        &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.action(&lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.parameter1, &lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.parameter2);
    }
}
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;(hay clases Task para uno, dos o tres parámetros).&lt;/p&gt;

&lt;p&gt;Pueden invocar GoRoutines.Go directamente, especificando una acción y sus parámetros:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;&lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;static&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;void&lt;/span&gt; Go&amp;lt;T1, T2&amp;gt;(Action&amp;lt;T1, T2&amp;gt; action, T1 parameter1, T2 parameter2)
{
    Go(&lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Task&amp;lt;T1, T2&amp;gt;(action, parameter1, parameter2));
}
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Podemos escribir esta invocación usando expresiones lambda:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;[TestMethod]
&lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;void&lt;/span&gt; RunGoRoutineWithTwoParameters()
{
    &lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; i = 0;
    AutoResetEvent handle = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; AutoResetEvent(&lt;span style="color:#0000ff;"&gt;false&lt;/span&gt;);
    GoRoutines.Go((x, y) =&amp;gt; { i = x + y; handle.Set(); }, 2, 3);
    handle.WaitOne();
    Assert.AreEqual(5, i);
}&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Con todo esto implementado, escribí una aplicación de consola&amp;#160; AjConcurr.Primes, reimplementando el ejemplo de números primos de mi anterior post:&lt;/p&gt;

&lt;pre&gt;&lt;pre style="background-color:#ffffff;margin:0em;width:100%;font-family:consolas,courier,monospace;font-size:12px;"&gt;Channel numbers = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Channel();
GoRoutines.Go(() =&amp;gt; { &lt;span style="color:#0000ff;"&gt;for&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; k = 2; ; k++) numbers.Send(k);  });
Channel channel = numbers;
&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; prime = 0;
&lt;span style="color:#0000ff;"&gt;while&lt;/span&gt; (prime &amp;lt; 1000)
{
    prime = (&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt;)channel.Receive();
    Console.WriteLine(prime);
    Channel newchannel = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; Channel();
    GoRoutines.Go((input, output, p) =&amp;gt;
    {
       &lt;span style="color:#0000ff;"&gt;while&lt;/span&gt; (&lt;span style="color:#0000ff;"&gt;true&lt;/span&gt;)
       {
           &lt;span style="color:#0000ff;"&gt;int&lt;/span&gt; number = (&lt;span style="color:#0000ff;"&gt;int&lt;/span&gt;)input.Receive();
           &lt;span style="color:#0000ff;"&gt;if&lt;/span&gt; ((number % p) != 0)
               output.Send(number);
       }
    }, channel, newchannel, prime);
    channel = newchannel;
}
&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Me gusta ver este código formateado en pastie:&lt;/p&gt;

&lt;p&gt;&lt;a title="http://pastie.org/761916" href="http://pastie.org/761916"&gt;http://pastie.org/761916&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Próximos pasos: &lt;/p&gt;

&lt;p&gt;- Mejorar Channel para soportar múltiples productores y consumidores de valores que operan de forma simultánea sobre el mismo canal. 
  &lt;br /&gt;- Agregar soporte de Futures

  &lt;br /&gt;- Agregar características de &lt;a href="http://en.wikipedia.org/wiki/Reactive_programming" target="_blank"&gt;reactive programming&lt;/a&gt; (sé que está el Reactive Framework, quería experimentar algo con código propio).&lt;/p&gt;

&lt;p&gt;Nos leemos!&lt;/p&gt;

&lt;p&gt;Angel “Java” Lopez 
  &lt;br /&gt;&lt;a href="http://www.ajlopez.com"&gt;http://www.ajlopez.com&lt;/a&gt; 

  &lt;br /&gt;&lt;a href="http://twitter.com/ajlopez"&gt;http://twitter.com/ajlopez&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1749362" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/lopez/archive/tags/.NET/default.aspx">.NET</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/C+Sharp/default.aspx">C Sharp</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/Programaci_F300_n/default.aspx">Programación</category></item><item><title>Code Kata: Capturando páginas web</title><link>http://msmvps.com/blogs/lopez/archive/2009/10/26/code-kata-capturando-p-225-ginas-web.aspx</link><pubDate>Mon, 26 Oct 2009 09:31:03 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1735292</guid><dc:creator>lopez</dc:creator><slash:comments>3</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/lopez/rsscomments.aspx?PostID=1735292</wfw:commentRss><comments>http://msmvps.com/blogs/lopez/archive/2009/10/26/code-kata-capturando-p-225-ginas-web.aspx#comments</comments><description>&lt;p&gt;&lt;img style="display:inline;margin:0px 20px 20px 0px;" src="http://www.todocontenidos.com/images/articles/scrap03.png" align="left" alt="" /&gt; Para practicar, como &lt;a href="http://codekata.pragprog.com/codekata/" target="_blank"&gt;Code Kata&lt;/a&gt; (y también, como prueba de concepto para un proyecto relacionado con &lt;a title="Hacia una Historia Clínica Digital de Código Abierto" href="http://msmvps.com/blogs/lopez/archive/2009/09/29/hacia-una-historia-cl-237-nica-digital-de-c-243-digo-abierto.aspx"&gt;Hacia una Historia Clínica Digital de Código Abierto&lt;/a&gt;), estuve codificando un programa de &lt;a href="http://en.wikipedia.org/wiki/Web_scraping" target="_blank"&gt;Web Scraping&lt;/a&gt;. Pueden bajarse el código completo desde: &lt;a href="http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/WebScrappingExample" target="_blank"&gt;Web Scrapping Example&lt;/a&gt; (pensé que en inglés era “scrapping” con doble p, así que quedó por ahora con ese nombre :-)&lt;/p&gt;  &lt;p&gt;La solución, escrita en C#, contiene un proyecto de librería de clases, uno de tests de Visual Studio, y un programa de demostración, del tipo consola. Fue interesante codificar un mini HTML parser (simple, que no contempla todos los casos, sólo los necesarios para el trabajo), que soporta el análisis de páginas mal formadas. El usar TDD (&lt;a href="http://en.wikipedia.org/wiki/Test-driven_development" target="_blank"&gt;Test-Driven Development&lt;/a&gt;) me permitió ir codificándolo de forma de estar seguro de lo que necesitaba como resultado, y además, que ese resultado fuera el correcto. Ejemplo de test simple:&lt;/p&gt;  &lt;div id="codeSnippetWrapper"&gt;   &lt;div id="codeSnippet" style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;     &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;[TestMethod]&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;&lt;span style="color:#0000ff;"&gt;public&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;void&lt;/span&gt; ParseSimpleTag()&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;{&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;    HtmlParser parser = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; HtmlParser(&lt;span style="color:#006080;"&gt;&amp;quot;&amp;lt;html&amp;gt;&amp;quot;&lt;/span&gt;);&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;    HtmlToken token = parser.NextToken();&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;&amp;#160;&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;    Assert.IsNotNull(token);&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;    Assert.AreEqual(HtmlTokenType.Tag, token.TokenType);&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;    Assert.AreEqual(&lt;span style="color:#006080;"&gt;&amp;quot;html&amp;quot;&lt;/span&gt;, token.Name);&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;&amp;#160;&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;    Assert.IsNull(parser.NextToken());&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;}&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Para capturar las páginas, usé System.Net.WebClient, con código tan simple como:&lt;/p&gt;

&lt;div id="codeSnippetWrapper"&gt;
  &lt;div id="codeSnippet" style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;
    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;&lt;span style="color:#0000ff;"&gt;private&lt;/span&gt; &lt;span style="color:#0000ff;"&gt;string&lt;/span&gt; GetContent()&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;{&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;    WebClient webclient = &lt;span style="color:#0000ff;"&gt;new&lt;/span&gt; WebClient();&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:#f4f4f4;text-align:left;border-bottom-style:none;"&gt;    &lt;span style="color:#0000ff;"&gt;return&lt;/span&gt; webclient.DownloadString(&lt;span style="color:#0000ff;"&gt;this&lt;/span&gt;.address);&lt;/pre&gt;


    &lt;pre style="padding-right:0px;padding-left:0px;font-size:8pt;padding-bottom:0px;margin:0em;overflow:visible;width:100%;color:black;direction:ltr;border-top-style:none;line-height:12pt;padding-top:0px;font-family:&amp;#39;Courier New&amp;#39;, courier, monospace;border-right-style:none;border-left-style:none;background-color:white;text-align:left;border-bottom-style:none;"&gt;}&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Como demostración, en el programa de consola, parto de explorar la página:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.nlm.nih.gov/medlineplus/druginfo/meds/a699012.html" target="_blank"&gt;Medline Plus Drugs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lanzando el programa de consola, aparece la salida, capturando algunos cientos de páginas, sobre información de drogas medicinales (efectos, posología, contraindicaciones, etc):&lt;/p&gt;

&lt;p&gt;&lt;img src="http://www.todocontenidos.com/images/articles/scrap02.png" alt="" /&gt; &lt;/p&gt;

&lt;p&gt;Exploro las páginas que apuntan a la información de drogas por letra, obtengo los enlaces a cada droga, traigo el contenido de la página, extraigo la información que me interesa (descartando el principio y el fin de la página) por cada droga. Un ejemplo de página:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://www.todocontenidos.com/images/articles/scrap01.png" alt="" /&gt; &lt;/p&gt;

&lt;p&gt;Al final, genero una página de índice:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://www.todocontenidos.com/images/articles/scrap04.png" alt="" /&gt; &lt;/p&gt;

&lt;p&gt;Podría implementar un mejor parser de HTML, y algunas operaciones más de extracción de datos. Pero para lo que quería obtener, me bastó. Y fue una buena experiencia de TDD.&lt;/p&gt;

&lt;p&gt;Si usan la información generada, recuerden de leer los términos del sitio original (entiendo que necesitan una autorización para usarlo de forma comercial, no parece que haya problema en capturar los datos en un utilitario no comercial).&lt;/p&gt;

&lt;p&gt;¿Por qué no explorar directamente la web para obtener estos datos? Porque el contexto de la prueba de concepto que me impuse, es que los médicos y enfermeros no tienen acceso a Internet desde todas las máquinas donde van a estar trabajando.&lt;/p&gt;

&lt;p&gt;Nos leemos!&lt;/p&gt;

&lt;p&gt;Angel “Java” Lopez 
  &lt;br /&gt;&lt;a href="http://www.ajlopez.com"&gt;http://www.ajlopez.com&lt;/a&gt; 

  &lt;br /&gt;&lt;a href="http://twitter.com/ajlopez"&gt;http://twitter.com/ajlopez&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1735292" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/lopez/archive/tags/.NET/default.aspx">.NET</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/C+Sharp/default.aspx">C Sharp</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/Programaci_F300_n/default.aspx">Programación</category><category domain="http://msmvps.com/blogs/lopez/archive/tags/Code+Katas/default.aspx">Code Katas</category></item></channel></rss>