<?xml version="1.0" encoding="UTF-8" ?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="es"><title type="html">vtortola</title><subtitle type="html">vtortola.NET</subtitle><id>http://blog.avanadeadvisor.com/blogs/vtortola/atom.aspx</id><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/default.aspx" /><link rel="self" type="application/atom+xml" href="http://blog.avanadeadvisor.com/blogs/vtortola/atom.aspx" /><generator uri="http://communityserver.org" version="2.0.60217.2664">Community Server</generator><updated>2007-09-13T20:45:36Z</updated><entry><title>Recorrer una estructura en arbol con multiples hilos: ThreadedTreeBase</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/13/5697.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/13/5697.aspx</id><published>2007-10-14T00:01:00Z</published><updated>2007-10-14T00:01:00Z</updated><content type="html">&lt;p&gt;Esta clase, fruto de un fin de semana de inspiración y una semana de depurado en mis ratos libres, que no son muchos, sigue el planteamiento anterior para &lt;a href="http://www.vtortola.net/post/Recorrer-una-estructura-en-arbol-con-multiples-hilos.aspx"&gt;recorrer estructuras en arbol con múltiples hilos&lt;/a&gt;. Se trata de una clase abstracta que encapsula todo el mecanismo multithreading, simplemente hay que implementar un método que define&amp;nbsp;como&amp;nbsp;se obtienen sus hijos y suscribirse a un evento que es disparado por cada nodo. &lt;/p&gt;
&lt;p&gt;Siempre es buena idea encapsular funcionalidades complejas de este tipo ya que:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Permite probar la funcionalidad en otros campos. 
&lt;/li&gt;&lt;li&gt;Permite hacer correciones de forma más sencilla. La lógica puede ser bastante complicada y no conviene mezclarla con la lógica de negocios. 
&lt;/li&gt;&lt;li&gt;Permite su reusabilidad.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;La clase utiliza un tipo genérico para que pueda adaptarse a cualquier tipo. Los nodos encontrados se envian vía evento, junto a un StringBuilder que indica su jerarquia en el arbol y&amp;nbsp;cuyo separador puede ser indicado también en una sobrecarga del constructor. Además, la clase argumento del evento&amp;nbsp;está implementado dentro de la clase, lo que permite que use el tipo genérico de la clase ThreadedTreeBase. Las excepciones capturadas se devuelven también en un evento, por lo que conviene comprobar si la propiedad &lt;i&gt;Error&lt;/i&gt; es &lt;i&gt;null&lt;/i&gt; ó no cuando evaluemos un nodo.&lt;/p&gt;
&lt;p&gt;[more]&lt;/p&gt;
&lt;p&gt;Hay un método que permite cancelar la operación si esta en curso por medio de la evaluación de una variable &lt;i&gt;run&lt;/i&gt; que se comprueba en cada ciclo.&lt;/p&gt;
&lt;p&gt;Cuando se construye, se le indica el número de hilos que podrá manejar y la prioridad que se le quiere asignar.&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;abstract&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ThreadedTreeBase&amp;lt;NodeType&amp;gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;where&lt;/span&gt; NodeType : &lt;span class="kwrd"&gt;class&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;{&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Objeto que controla el fin de la ejecución&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; Object endLock = &lt;span class="kwrd"&gt;new&lt;/span&gt; Object();&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Delegado para recursividad&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;delegate&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; runDlgt_(NodeType Node, StringBuilder Path);&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; runDlgt_ runDlgt;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Control de hilos.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; Int32 maxThreads;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; Int32 runningThreads = 0;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Int32 MaxThreads { get { &lt;span class="kwrd"&gt;return&lt;/span&gt; maxThreads; } }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Indicador de nodos por procesar&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; Int32 remainingNodes = 0;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Control de prioridad de ejecución.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; ThreadPriority priority;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; ThreadPriority Priority { get { &lt;span class="kwrd"&gt;return&lt;/span&gt; priority; } }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Control de cancelación.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; Int32 runFlag = 1;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Caracter delimitador de la ruta xpath&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; Char xpathDelimiter = &lt;span class="str"&gt;'/'&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Char XpathDelimiter { get { &lt;span class="kwrd"&gt;return&lt;/span&gt; xpathDelimiter; } }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Evento de nodo procesado&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;delegate&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; ProcessedNode_(ProcessedNodeEventArgs e);&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;event&lt;/span&gt; ProcessedNode_ ProcessedNode;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ProcessedNodeEventArgs : EventArgs&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; NodeType node;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; String xpath;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; Exception error;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; NodeType Node { get { &lt;span class="kwrd"&gt;return&lt;/span&gt; node; } }&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; String XPath { get { &lt;span class="kwrd"&gt;return&lt;/span&gt; xpath; } }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; Exception Error { get { &lt;span class="kwrd"&gt;return&lt;/span&gt; error; } }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; ProcessedNodeEventArgs(NodeType Nodo, &lt;/pre&gt;&lt;pre&gt;                                  String XPath, &lt;/pre&gt;&lt;pre class="alt"&gt;                                  Exception Error)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;this&lt;/span&gt;.node = Nodo;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;this&lt;/span&gt;.xpath = XPath;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;this&lt;/span&gt;.error = Error;&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// Constructor protegido.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;param name="MaxThreads"&amp;gt;Máximo de tareas concurrentes.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;param name="Priority"&amp;gt;Indica la prioridad de los hilos.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; ThreadedTreeBase(Int32 MaxThreads, ThreadPriority Priority)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    runDlgt = &lt;span class="kwrd"&gt;new&lt;/span&gt; runDlgt_(Run);&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;/pre&gt;&lt;pre&gt;    &lt;span class="rem"&gt;// Resto un hilo ya que hay que &lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="rem"&gt;// contar con el thread que inicia &lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="rem"&gt;// la clase&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.maxThreads = MaxThreads-1;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.priority = Priority;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; ThreadedTreeBase(Int32 MaxThreads, ThreadPriority Priority, &lt;/pre&gt;&lt;pre&gt;                             Char XpathDelimiter): &lt;span class="kwrd"&gt;this&lt;/span&gt;(MaxThreads, Priority)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.xpathDelimiter = XpathDelimiter;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// Iniciar navegación del arbol.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;param name="Parte"&amp;gt;Parte inicial.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; RunBrowser(NodeType nodo)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      runDlgt.BeginInvoke(nodo, &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder(),&lt;/pre&gt;&lt;pre&gt;        &lt;span class="kwrd"&gt;delegate&lt;/span&gt;(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;        {&lt;/pre&gt;&lt;pre&gt;          &lt;span class="kwrd"&gt;try&lt;/span&gt; { runDlgt.EndInvoke(ia); }&lt;/pre&gt;&lt;pre class="alt"&gt;          &lt;span class="kwrd"&gt;catch&lt;/span&gt; (Exception Ex)&lt;/pre&gt;&lt;pre&gt;          {&lt;/pre&gt;&lt;pre class="alt"&gt;            Debug.Fail(Ex.Message, Ex.StackTrace);&lt;/pre&gt;&lt;pre&gt;            sendNodeData(nodo ?? &lt;span class="kwrd"&gt;default&lt;/span&gt;(NodeType), &lt;span class="kwrd"&gt;null&lt;/span&gt;, Ex);&lt;/pre&gt;&lt;pre class="alt"&gt;          }&lt;/pre&gt;&lt;pre&gt;        }, &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;      Monitor.Wait(endLock);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// Inicia recorrido desde nodo inicial.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;param name="nodo"&amp;gt;Nodo inicial&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Run(NodeType Node, StringBuilder path)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="rem"&gt;// Compruebo la cancelación (nuevas bifurcaciones).&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (Thread.VolatileRead(&lt;span class="kwrd"&gt;ref&lt;/span&gt; runFlag) == 0) &lt;span class="kwrd"&gt;return&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="rem"&gt;// Asigno la prioridad al thread.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    Thread.CurrentThread.Priority = &lt;span class="kwrd"&gt;this&lt;/span&gt;.priority;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="rem"&gt;// Obtengo la jerarquia&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    path = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder(path.ToString());&lt;/pre&gt;&lt;pre class="alt"&gt;    path.AppendFormat(&lt;span class="str"&gt;"{0}{1}"&lt;/span&gt;,&lt;span class="kwrd"&gt;this&lt;/span&gt;.xpathDelimiter, Node);&lt;/pre&gt;&lt;pre&gt;    &lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="rem"&gt;// Envio el nodo vía evento&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    sendNodeData(Node, path, &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="rem"&gt;// Obtengo los subnodos y los contabilizo&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    NodeType[] childs = getNodeChilds(Node);&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock) remainingNodes += childs.Length;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;/pre&gt;&lt;pre&gt;    &lt;span class="rem"&gt;// Recorro la lista de nodos hijos...&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (NodeType child &lt;span class="kwrd"&gt;in&lt;/span&gt; childs)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// Compruebo la cancelación dentro del bucle.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// (bifurcaciones en curso).&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (Thread.VolatileRead(&lt;span class="kwrd"&gt;ref&lt;/span&gt; runFlag) == 0) &lt;span class="kwrd"&gt;return&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// ... llamando recursivamente a cada uno de ellos..&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (Thread.VolatileRead(&lt;span class="kwrd"&gt;ref&lt;/span&gt; runningThreads) &amp;lt; maxThreads)&lt;/pre&gt;&lt;pre class="alt"&gt;      {&lt;/pre&gt;&lt;pre&gt;        &lt;span class="rem"&gt;// Incremento el número de hilos secundarios en ejecución.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;        Interlocked.Increment(&lt;span class="kwrd"&gt;ref&lt;/span&gt; runningThreads);&lt;/pre&gt;&lt;pre&gt;        &lt;span class="rem"&gt;// Ejecuto en otro hilo.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;        runDlgt.BeginInvoke(child, path,&lt;/pre&gt;&lt;pre&gt;          &lt;span class="kwrd"&gt;delegate&lt;/span&gt;(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;            {&lt;/pre&gt;&lt;pre&gt;              &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;              {&lt;/pre&gt;&lt;pre&gt;                Monitor.Enter(endLock);&lt;/pre&gt;&lt;pre class="alt"&gt;                runDlgt.EndInvoke(ia);&lt;/pre&gt;&lt;pre&gt;              }&lt;/pre&gt;&lt;pre class="alt"&gt;              &lt;span class="kwrd"&gt;catch&lt;/span&gt; (Exception Ex)&lt;/pre&gt;&lt;pre&gt;              {&lt;/pre&gt;&lt;pre class="alt"&gt;                Debug.Fail(Ex.Message, Ex.StackTrace);&lt;/pre&gt;&lt;pre&gt;                NodeType userState = ia.AsyncState &lt;span class="kwrd"&gt;as&lt;/span&gt; NodeType;&lt;/pre&gt;&lt;pre class="alt"&gt;                sendNodeData(userState ?? &lt;span class="kwrd"&gt;default&lt;/span&gt;(NodeType), &lt;span class="kwrd"&gt;null&lt;/span&gt;, Ex);&lt;/pre&gt;&lt;pre&gt;              }&lt;/pre&gt;&lt;pre class="alt"&gt;              &lt;span class="kwrd"&gt;finally&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;              {&lt;/pre&gt;&lt;pre class="alt"&gt;                runningThreads--;&lt;/pre&gt;&lt;pre&gt;                remainingNodes--;&lt;/pre&gt;&lt;pre class="alt"&gt;                &lt;span class="rem"&gt;// Se pulsa sobre 'endLock' si no hay hilos &lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;                &lt;span class="rem"&gt;// secundarios en ejecución ni nodos pendientes.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;                &lt;span class="kwrd"&gt;if&lt;/span&gt; ((runningThreads == 0) &amp;amp;&amp;amp; (remainingNodes == 0))&lt;/pre&gt;&lt;pre&gt;                {&lt;/pre&gt;&lt;pre class="alt"&gt;                  Monitor.Pulse(endLock);&lt;/pre&gt;&lt;pre&gt;                }&lt;/pre&gt;&lt;pre class="alt"&gt;                Monitor.Exit(endLock);&lt;/pre&gt;&lt;pre&gt;              }&lt;/pre&gt;&lt;pre class="alt"&gt;            }, child);&lt;/pre&gt;&lt;pre&gt;      }&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      {&lt;/pre&gt;&lt;pre class="alt"&gt;        &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;        {&lt;/pre&gt;&lt;pre class="alt"&gt;          runDlgt.Invoke(child, path);&lt;/pre&gt;&lt;pre&gt;          &lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre class="alt"&gt;          {&lt;/pre&gt;&lt;pre&gt;            remainingNodes--;&lt;/pre&gt;&lt;pre class="alt"&gt;            &lt;span class="kwrd"&gt;if&lt;/span&gt; ((runningThreads == 0) &amp;amp;&amp;amp; (remainingNodes == 0))&lt;/pre&gt;&lt;pre&gt;            {&lt;/pre&gt;&lt;pre class="alt"&gt;              Monitor.Pulse(endLock);&lt;/pre&gt;&lt;pre&gt;            }&lt;/pre&gt;&lt;pre class="alt"&gt;          }&lt;/pre&gt;&lt;pre&gt;        }&lt;/pre&gt;&lt;pre class="alt"&gt;        &lt;span class="kwrd"&gt;catch&lt;/span&gt; (Exception Ex)&lt;/pre&gt;&lt;pre&gt;        {&lt;/pre&gt;&lt;pre class="alt"&gt;          Debug.Fail(Ex.Message, Ex.StackTrace);&lt;/pre&gt;&lt;pre&gt;          sendNodeData(child ?? &lt;span class="kwrd"&gt;default&lt;/span&gt;(NodeType), &lt;span class="kwrd"&gt;null&lt;/span&gt;, Ex);&lt;/pre&gt;&lt;pre class="alt"&gt;        }&lt;/pre&gt;&lt;pre&gt;      }&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// Envia los datos vía evento.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;param name="nodo"&amp;gt;Nodo del que se obtuvo la información.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;param name="infoNodo"&amp;gt;Información.&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; sendNodeData(NodeType nodo, &lt;/pre&gt;&lt;pre&gt;                            StringBuilder path, Exception Ex)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    if(ProcessedNode!=null)ProcessedNode.Invoke(&lt;span class="kwrd"&gt;new&lt;/span&gt; ProcessedNodeEventArgs&lt;/pre&gt;&lt;pre class="alt"&gt;      (nodo, path == &lt;span class="kwrd"&gt;null&lt;/span&gt; ? String.Empty : path.ToString(), Ex));&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// Cancela la ejecución.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; CancelBrowse()&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="rem"&gt;// Full memory barrier&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    Interlocked.Exchange(&lt;span class="kwrd"&gt;ref&lt;/span&gt; runFlag, 0);&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// Función a implementar con la forma de obtener &lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// los nodos hijos de otro nodo.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;/// &amp;lt;param name="nodo"&amp;gt;Nodo padre&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;/// &amp;lt;returns&amp;gt;Lista de nodos&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;abstract&lt;/span&gt; NodeType[] getNodeChilds(NodeType nodo);&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ahora un ejemplo de su implementación. El sistema de archivos es a fin de cuentas, una estructura en arbol, así que voy a hacer una pequeña implementación de esta clase que me diga cuantos archivos contiene un directorio contando los que hay en sus subdirectorios... en multihilo, por supuesto :D&lt;/p&gt;
&lt;p&gt;Evidentemente, no tiene utilidad práctica lanzar múltiples hilos contra un recurso local, con latencia mínima y compartido para todos los hilos ... simplemente como prueba:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; ThreadedTreeFileSystem &lt;/pre&gt;&lt;pre&gt;  : ThreadedTreeBase&amp;lt;FileSystemInfo&amp;gt;&lt;/pre&gt;&lt;pre class="alt"&gt;{&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Contador &lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;long&lt;/span&gt; counter = 0;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Int64 Contador&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; Thread.VolatileRead(&lt;span class="kwrd"&gt;ref&lt;/span&gt; counter); }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; ThreadedTreeFileSystem(&lt;span class="kwrd"&gt;int&lt;/span&gt; MaxThreads, ThreadPriority p)&lt;/pre&gt;&lt;pre&gt;      : &lt;span class="kwrd"&gt;base&lt;/span&gt;(MaxThreads, p)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="rem"&gt;// Me subscribo al evento que devuelve los nodos.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.ProcessedNode += &lt;span class="kwrd"&gt;new&lt;/span&gt; ThreadedTreeBase&amp;lt;FileSystemInfo&amp;gt;.ProcessedNode_&lt;/pre&gt;&lt;pre&gt;      (ExplosionSistemaDeArchivos_ProcessedNode);&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;void&lt;/span&gt; ExplosionSistemaDeArchivos_ProcessedNode&lt;/pre&gt;&lt;pre&gt;    (ThreadedTreeBase&amp;lt;FileSystemInfo&amp;gt;.ProcessedNodeEventArgs e)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="rem"&gt;// Si el nodo es un archivo, lo cuento y lo muestro.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (e.Node &lt;span class="kwrd"&gt;is&lt;/span&gt; FileInfo)&lt;/pre&gt;&lt;pre&gt;      Interlocked.Increment(&lt;span class="kwrd"&gt;ref&lt;/span&gt; counter);&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; FileSystemInfo[] getNodeChilds(FileSystemInfo nodo)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="rem"&gt;// Obtengo la lista de subnodos.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    List&amp;lt;FileSystemInfo&amp;gt; array = &lt;span class="kwrd"&gt;new&lt;/span&gt; List&amp;lt;FileSystemInfo&amp;gt;();&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (nodo &lt;span class="kwrd"&gt;is&lt;/span&gt; DirectoryInfo)&lt;/pre&gt;&lt;pre&gt;      {&lt;/pre&gt;&lt;pre class="alt"&gt;        array.AddRange(((DirectoryInfo)nodo).GetDirectories(&lt;span class="str"&gt;"*"&lt;/span&gt;));&lt;/pre&gt;&lt;pre&gt;        array.AddRange(((DirectoryInfo)nodo).GetFiles());&lt;/pre&gt;&lt;pre class="alt"&gt;      }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;catch&lt;/span&gt; (SecurityException sEx)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      Console.WriteLine(&lt;span class="str"&gt;"{0} : {1}"&lt;/span&gt;, sEx.GetType().Name, sEx.Message);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;return&lt;/span&gt; array.ToArray();&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;La implementación tiene&amp;nbsp;cuatro partes esenciales:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;La clase se define heredando de ThreadedTreeBase y especificando el tipo que usaremos como nodo. 
&lt;/li&gt;&lt;li&gt;En el constructor, se pasan los parámetros adecuados y se define el evento. 
&lt;/li&gt;&lt;li&gt;En el manejador del evento, se procesa el nodo, en este caso, únicamente se cuentan los archivos y se muestra su nombre. También podríamos ver la jerarquia en &lt;i&gt;e.XPath&lt;/i&gt;. 
&lt;/li&gt;&lt;li&gt;En GetNodeChilds, el método que debemos implementar para completar la clase abstracta, esta la lógica necesaria para obtener los subnodos de un nodo y lo devuelve en forma de array.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Es importante remarcar, que aunque todo el mecanismo multithreading esta encapsulado, seguimos necesitando sincronizar los accesos a la memoria compartida, como por ejemplo el acceso a &lt;i&gt;counter&lt;/i&gt;, que se incrementa con &lt;i&gt;Interlocked&lt;/i&gt; para evitar&amp;nbsp;&lt;i&gt;race conditions&lt;/i&gt; (condiciones de anticipación).&lt;/p&gt;
&lt;p&gt;Para su uso...&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; folder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);&lt;/pre&gt;&lt;pre&gt;  &lt;/pre&gt;&lt;pre class="alt"&gt;ThreadedTreeFileSystem browser =&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;new&lt;/span&gt; ThreadedTreeFileSystem(5, ThreadPriority.Normal);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;browser.RunBrowser(&lt;span class="kwrd"&gt;new&lt;/span&gt; DirectoryInfo(folder));&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;Console.WriteLine(&lt;span class="str"&gt;"-- Archivos: "&lt;/span&gt; + browser.Contador);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cualquier &lt;i&gt;feedback&lt;/i&gt; sobre esta clase ó su diseño es bienvenida :)&lt;/p&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Recorrer-una-estructura-en-arbol-con-multiples-hilos-ThreadedTreeBase.aspx"&gt;Recorrer una estructura en arbol con multiples hilos: ThreadedTreeBase | vtortola.NET&lt;/a&gt;&lt;/h5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5697" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>Recorrer una estructura en arbol con m&amp;amp;#250;ltiples hilos</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/13/5693.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/13/5693.aspx</id><published>2007-10-13T20:45:16Z</published><updated>2007-10-13T20:45:16Z</updated><content type="html">&lt;p&gt;Una estructura arbol se recorre por medio de una función recursiva empezando desde el nodo raiz por medio de dos operaciones básicas, un método &lt;em&gt;ProcessNode&lt;/em&gt; con el que procesamos la información de ese nodo, y otro &lt;em&gt;GetNodeChilds&lt;/em&gt; con el que obtenemos los nodos hijos de un nodo, sería algo así:&lt;/p&gt; &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; BrowseTree(Node root)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  ProcessNode(root);&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (Node n &lt;span class="kwrd"&gt;in&lt;/span&gt; GetNodeChilds(root))&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    BrowseTree(n);&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pero cuando esa estructura en arbol es realmente grande, ó alguna de las dos operaciones conlleva una latencia significativa, como puede ser por ejemplo&amp;nbsp;procesar cada nodo localmente u obtener los hijos de un nodo a través de la red,&amp;nbsp;puede ser muy útil recorrer dicha estructura con múltiples hilos si se dan&amp;nbsp;las condiciones adecuadas, que pueden ser:&lt;/p&gt;
&lt;p&gt;[more]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Latencia elevada que provoca tiempos de &lt;em&gt;Idle&lt;/em&gt; significativos al obtener los hijos. 
&lt;li&gt;Múltiples CPUs en la máquina que navega el arbol. 
&lt;li&gt;Múltiples CPUs en la máquina que contiene el arbol, capacidad para atender peticiones concurentes y preferiblemente concurrencia optimista.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;El algorrítmo se complica un poco, pero gracias al modelo de programación asíncrono podemos simplificar bastante. A priori, puede parece que es tan simple como lanzar un hilo por cada nodo hijo del nodo raiz, pero&amp;nbsp;si el nodo raiz tiene muchos hijos en su primer nivel podemos dejar vacio el &lt;em&gt;ThreadPool&lt;/em&gt;&amp;nbsp;pudiendo causar un deadlock&amp;nbsp;ó en caso de usar &lt;em&gt;Threads&lt;/em&gt; convencionales llegar a un número contraproducente de ellos, por lo que debemos controlar cuantos hilos vamos a usar para recorrer el arbol. Pero no esta todo solucionado,&amp;nbsp;si el primer nodo hijo tiene 4 hijos, y el segundo nodo hijo tiene 400... el hilo que se encargue del primer nodo quedará desaprovechado mientras que el que se encargue del segundo tendrá mucho trabajo por delante, por lo que debemos poder&amp;nbsp;reutilizar los hilos de forma dinámica.&lt;/p&gt;
&lt;p&gt;Estos dos problemas quedan resueltos con el siguiente modelo:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Estos ejemplos de código son orientativos y simplificados.&lt;/em&gt;&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; BrowseTreeDlgt_ BrowseTreeDlgt = &lt;span class="kwrd"&gt;new&lt;/span&gt; BrowseTreeDlgt_(BrowseTree);&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; BrowseTree(Node root)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  ProcessNode(root);&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (Node n &lt;span class="kwrd"&gt;in&lt;/span&gt; GetNodeChilds(root))&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (runningThreads&amp;lt;maxThreads)&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      runningThreads++;&lt;/pre&gt;&lt;pre class="alt"&gt;      BrowseTreeDlgt.BeginInvoke(n,&lt;/pre&gt;&lt;pre&gt;                                 &lt;span class="kwrd"&gt;delegate&lt;/span&gt;(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;                                 {&lt;/pre&gt;&lt;pre&gt;                                   BrowseTreeDlgt.EndInvoke(ia);&lt;/pre&gt;&lt;pre class="alt"&gt;                                   runningThreads--;&lt;/pre&gt;&lt;pre&gt;                                 },&lt;/pre&gt;&lt;pre class="alt"&gt;                                 &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      BrowseTreeDlgt.Invoke(n);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;maxThreads&lt;/em&gt; define el número máximo de hilos a usar y &lt;em&gt;runningThreads&lt;/em&gt; los que tenemos ya en funcionamiento, si hay hilos disponibles se lanza el nodo en otro hilo (.BeginInvoke)&amp;nbsp;y si no, pues se continua con el hilo actual (.Invoke) como si de una función recursiva se tratase. Cada vez que creamos un hilo, incrementamos &lt;em&gt;runningThreads&lt;/em&gt; y lo decrementamos cuando acaba, pero ... ¿en que orden exactamente? Pues el incremento lo antes posible y el decremento lo más tarde posible, de forma que se reduzca la posiblidad de crear hilos de más. De esta forma controlamos la cantidad de hilos.&lt;/p&gt;
&lt;p&gt;Este modelo tiene una pega particular, y es el hecho de que cuando lanzamos nodos con todos sus hijos en otro hilo, la ejecución del hilo principal llegará al final y no sabremos si el resto de hilos secundarios han acabado ya de procesar todos sus nodos. El mejor planteamiento es bloquear el hilo que llama a la función hasta que se acabe de recorrer el arbol, para ello bloqueamos dicho hilo y cuando el número de hilos secundarios sea 0, lo liberamos. Sería algo así:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; BrowseTree(Node root)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  ProcessNode(root);&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (Node n &lt;span class="kwrd"&gt;in&lt;/span&gt; GetNodeChilds(root))&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (runningThreads&amp;lt;maxThreads)&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      runningThreads++;&lt;/pre&gt;&lt;pre class="alt"&gt;      BrowseTreeDlgt.BeginInvoke(n,&lt;/pre&gt;&lt;pre&gt;                                 &lt;span class="kwrd"&gt;delegate&lt;/span&gt;(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;                                 {&lt;/pre&gt;&lt;pre&gt;                                   BrowseTreeDlgt.EndInvoke(ia);&lt;/pre&gt;&lt;pre class="alt"&gt;                                   runningThreads--;&lt;/pre&gt;&lt;pre&gt;                                   &lt;span class="kwrd"&gt;if&lt;/span&gt; (runningThreads == 0)&lt;/pre&gt;&lt;pre class="alt"&gt;                                     &lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre&gt;                                       Monitor.Pulse(endLock);&lt;/pre&gt;&lt;pre class="alt"&gt;                                 },&lt;/pre&gt;&lt;pre&gt;                                 &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      BrowseTreeDlgt.Invoke(n);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Y al llamar a la función:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  BrowseTree(node);&lt;/pre&gt;&lt;pre&gt;  Monitor.Wait(endLock);&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Esto hará que se llame a &lt;em&gt;BrowseTree&lt;/em&gt; y cuando termine el hilo principal quede bloquedado hasta que &lt;em&gt;runningThreads&lt;/em&gt; sea 0. Pero siguen habiendo problemas... :D&lt;/p&gt;
&lt;p&gt;Una de las cosas que debemos tener en cuenta en entornos multithreading es que no hay un orden concreto y que la memoria puede ser alterada en cualquier momento, incluso entre instrucción e instrucción. Si por cualquier motivo, el hilo principal acaba después de los hilos secundarios... (por que le ha tocado procesar más nodos..) ... el &lt;em&gt;Pulse&lt;/em&gt; llegaría antes que el &lt;em&gt;Wait&lt;/em&gt;, con lo que la aplicación quedaría en un &lt;em&gt;deadlock&lt;/em&gt;. Para evitar esta situación, lo ideal es llamar a &lt;em&gt;BrowseTree&lt;/em&gt; asíncronamente de forma que el hilo que llama a la función quede bloqueado en el &lt;em&gt;Wait&lt;/em&gt; inmediatamente y aunque añadamos un hilo adicional... la carga es la misma:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  BrowseTreeDlgt.BeginInvoke(node,&lt;/pre&gt;&lt;pre&gt;                             &lt;span class="kwrd"&gt;delegate&lt;/span&gt;(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;                             {BrowseTreeDlgt.EndInvoke(ia);}, &lt;/pre&gt;&lt;pre&gt;                             &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;  Monitor.Wait(endLock);&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ahora que esta resuelto el tema de &lt;em&gt;"bloquear hasta terminar"&lt;/em&gt;, hay que dar un repaso a la condición que lo desbloquea, la de que los hilos secundarios en ejecución sea 0... Como decía la memoria puede ser alterada y/ó evaludada en cualquier momento, entonces se puede dar la situación (&lt;em&gt;de hecho se da&lt;/em&gt;)&amp;nbsp;de que se haga un &lt;em&gt;Pulse&lt;/em&gt; sobre el bloqueo mientras que se esté a punto de iniciar otro hilo secundario porque hay más nodos que procesar en el bucle... Por este motivo, hay que tener en cuenta si hay nodos por procesar antes de hacer el &lt;em&gt;Pulse&lt;/em&gt;, es decir, la condición para que se libere el bloqueo y por tanto se de por terminado el recorrido por el arbol es, que no haya hilos en ejecución y que los nodos por procesar sean 0, con lo que se asegura&amp;nbsp; que cuando se den estas dos condiciones&amp;nbsp;es el último hilo en ejecución. Ahora debemos comprobar tanto al final de las ejecuciones síncronas como asíncronas:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; BrowseTree(Node root)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  ProcessNode(root);&lt;/pre&gt;&lt;pre&gt;  Node[] childs =GetNodeChilds(root);&lt;/pre&gt;&lt;pre class="alt"&gt;  nodesRemaining += childs.Length;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (Node n &lt;span class="kwrd"&gt;in&lt;/span&gt; childs)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (runningThreads &amp;lt; maxThreads)&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      runningThreads++;&lt;/pre&gt;&lt;pre class="alt"&gt;      BrowseTreeDlgt.BeginInvoke(n,&lt;/pre&gt;&lt;pre&gt;                                 &lt;span class="kwrd"&gt;delegate&lt;/span&gt;(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;                                 {&lt;/pre&gt;&lt;pre&gt;                                   BrowseTreeDlgt.EndInvoke(ia);&lt;/pre&gt;&lt;pre class="alt"&gt;                                   runningThreads--;&lt;/pre&gt;&lt;pre&gt;                                   nodesRemaining--;&lt;/pre&gt;&lt;pre class="alt"&gt;                                   &lt;span class="kwrd"&gt;if&lt;/span&gt; ((runningThreads == 0)&amp;amp;&amp;amp;&lt;/pre&gt;&lt;pre&gt;                                       (nodesRemaining==0))&lt;/pre&gt;&lt;pre class="alt"&gt;                                     &lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre&gt;                                       Monitor.Pulse(endLock);&lt;/pre&gt;&lt;pre class="alt"&gt;                                 },&lt;/pre&gt;&lt;pre&gt;                                 &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      BrowseTreeDlgt.Invoke(n);&lt;/pre&gt;&lt;pre class="alt"&gt;      nodesRemaining--;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; ((runningThreads == 0) &amp;amp;&amp;amp; &lt;/pre&gt;&lt;pre class="alt"&gt;          (nodesRemaining == 0))&lt;/pre&gt;&lt;pre&gt;        &lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre class="alt"&gt;          Monitor.Pulse(endLock);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Como handicap el uso de multiples hilos trae los problemas que implica la sincronización, cosa en la que habrá que poner sumo cuidado y ... sobre todo ... imaginación, la principal herramienta del programador.&amp;nbsp; La forma ideal de ejecutar esta función sería algo más compleja:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; BrowseTreeDlgt_ BrowseTreeDlgt = &lt;span class="kwrd"&gt;new&lt;/span&gt; BrowseTreeDlgt_(BrowseTree);&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; BrowseTree(Node root)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  ProcessNode(root);&lt;/pre&gt;&lt;pre&gt;  Node[] childs =GetNodeChilds(root);&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;lock&lt;/span&gt;(endLock)&lt;/pre&gt;&lt;pre class="alt"&gt;    nodesRemaining += childs.Length;&lt;/pre&gt;&lt;pre&gt;  &lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;foreach&lt;/span&gt; (Node n &lt;span class="kwrd"&gt;in&lt;/span&gt; childs)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (runningThreads &amp;lt; maxThreads)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      Interlocked.Increment(&lt;span class="kwrd"&gt;ref&lt;/span&gt; runningThreads);&lt;/pre&gt;&lt;pre&gt;      BrowseTreeDlgt.BeginInvoke(n,&lt;/pre&gt;&lt;pre class="alt"&gt;                                 &lt;span class="kwrd"&gt;delegate&lt;/span&gt;(IAsyncResult ia)&lt;/pre&gt;&lt;pre&gt;                                 {&lt;/pre&gt;&lt;pre class="alt"&gt;                                   &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;                                   {&lt;/pre&gt;&lt;pre class="alt"&gt;                                     Monitor.Enter(endLock);&lt;/pre&gt;&lt;pre&gt;                                     BrowseTreeDlgt.EndInvoke(ia);&lt;/pre&gt;&lt;pre class="alt"&gt;                                     runningThreads--;&lt;/pre&gt;&lt;pre&gt;                                     nodesRemaining--;&lt;/pre&gt;&lt;pre class="alt"&gt;                                   }&lt;/pre&gt;&lt;pre&gt;                                   &lt;span class="kwrd"&gt;finally&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;                                   {&lt;/pre&gt;&lt;pre&gt;                                     &lt;span class="kwrd"&gt;if&lt;/span&gt; ((runningThreads == 0) &amp;amp;&amp;amp;&lt;/pre&gt;&lt;pre class="alt"&gt;                                         (nodesRemaining == 0))&lt;/pre&gt;&lt;pre&gt;                                       Monitor.Pulse(endLock);&lt;/pre&gt;&lt;pre class="alt"&gt;                                     Monitor.Exit(endLock);&lt;/pre&gt;&lt;pre&gt;                                   }&lt;/pre&gt;&lt;pre class="alt"&gt;                                 },&lt;/pre&gt;&lt;pre&gt;                                 &lt;span class="kwrd"&gt;null&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      BrowseTreeDlgt.Invoke(n);&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;lock&lt;/span&gt; (endLock)&lt;/pre&gt;&lt;pre&gt;      {&lt;/pre&gt;&lt;pre class="alt"&gt;        nodesRemaining--;&lt;/pre&gt;&lt;pre&gt;        &lt;span class="kwrd"&gt;if&lt;/span&gt; ((runningThreads == 0) &amp;amp;&amp;amp;&lt;/pre&gt;&lt;pre class="alt"&gt;            (nodesRemaining == 0))&lt;/pre&gt;&lt;pre&gt;            Monitor.Pulse(endLock);&lt;/pre&gt;&lt;pre class="alt"&gt;      }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Como se puede observar, el principal problema es no saber cuantos nodos tiene el arbol hasta que se han recorrido todos, pero con este planteamiento queda resuelto, al menos es lo que dicen las pruebas concienzudas con distintas CPUs y sistemas operativos que he hecho.&lt;/p&gt;
&lt;p&gt;En el próximo artículo un ejemplo de&amp;nbsp;una clase que realiza esta&amp;nbsp;tarea&amp;nbsp;mismo, con ejemplos incluidos&amp;nbsp;... ;)&lt;/p&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Recorrer-una-estructura-en-arbol-con-multiples-hilos.aspx"&gt;Recorrer una estructura en arbol con múltiples hilos | vtortola.NET&lt;/a&gt;&lt;/h5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5693" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>Cliente FTP as&amp;amp;#237;ncrono</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/07/5566.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/07/5566.aspx</id><published>2007-10-07T23:38:00Z</published><updated>2007-10-07T23:38:00Z</updated><content type="html">&lt;p&gt;Este es un (largo) ejemplo de como crear un cliente FTP asíncrono que gestione múltiples descargas de forma paralela. En este modelo, generalmente la lógica de la aplicación suele estar compuesta por métodos estáticos, pero se añade un objeto de estado (objectState)&amp;nbsp;que realiza el seguimiento&amp;nbsp;de la tarea durante las fases de la aplicación, el estado de la tarea puede notificarse mediante eventos por cambio, temporizados ó devolver el mismo objeto de estado para su seguimiento externo.&lt;/p&gt; &lt;p&gt;En este caso, he optado por devolver el propio objeto de estado pero protegido con una &lt;a href="http://www.vtortola.net/post/El-uso-de-interfaces.aspx"&gt;interfaz&lt;/a&gt; para que solo determinados miembros puedan ser accesibles y solo para lectura, el final de la tarea se indica con un evento que trae el mismo tipo de objeto. Guardar el objeto de estado y monitorizarlo ó simplemente esperar al evento de fin... queda a la libre elección.&lt;/p&gt; &lt;p&gt;[more]&lt;/p&gt; &lt;p&gt;La interfaz y el delegado que define al evento:&lt;/p&gt; &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;interface&lt;/span&gt; IFtpDownloadInfo&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  Int32 ID{get;}&lt;/pre&gt;&lt;pre&gt;  Boolean Ended{get;}&lt;/pre&gt;&lt;pre class="alt"&gt;  Int64 Donwloaded{get;}&lt;/pre&gt;&lt;pre&gt;  Exception Error{get;}&lt;/pre&gt;&lt;pre class="alt"&gt;  Int64 ElapsedMilliseconds{get;}&lt;/pre&gt;&lt;pre&gt;  Int64 Length{get;}&lt;/pre&gt;&lt;pre class="alt"&gt;  String LocalDirectory{get;}&lt;/pre&gt;&lt;pre&gt;  String RemoteFile{get;}&lt;/pre&gt;&lt;pre class="alt"&gt;  String FileName { get;}&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;delegate&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; FtpEventDlg_(IFtpDownloadInfo e);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;La clase que define el objeto de estado cumple esta interfaz, de forma que cuando enviemos el evento, relamente se pasa dicho objeto, pero solo serán accesibles los miembros de dicha interfaz. Esta clase contiene toda la información y campos necesarios para realizar el tracking de la descarga, además implementa &lt;a href="http://www.vtortola.net/post/Objetos-desechables-con-la-interfaz-IDisposable.aspx"&gt;el patrón desechable&lt;/a&gt; para poder liberar los recursos correctamente una vez haya terminado. El método .ToString ha sido redefinido para poder obtener una información rápida del objeto:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;internal&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; FtpDownloadState:IFtpDownloadInfo,IDisposable&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;readonly&lt;/span&gt; Int32 id;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; Int64 length = 0;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; Int64 downloadedBytes = 0;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; String localDirectory;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; String remoteFile;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; String fileName;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; Exception error;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Int32 ID{&lt;/pre&gt;&lt;pre&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; id; }}&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Boolean Ended { &lt;/pre&gt;&lt;pre class="alt"&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; (downloadedBytes == length)&amp;amp;&amp;amp;(length!=0);}}&lt;/pre&gt;&lt;pre&gt;  &lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Int64 Donwloaded { &lt;/pre&gt;&lt;pre&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; Interlocked.Read(&lt;span class="kwrd"&gt;ref&lt;/span&gt; downloadedBytes); }&lt;/pre&gt;&lt;pre class="alt"&gt;    set { Interlocked.Exchange(&lt;span class="kwrd"&gt;ref&lt;/span&gt; downloadedBytes,&lt;span class="kwrd"&gt;value&lt;/span&gt;); }}&lt;/pre&gt;&lt;pre&gt;  &lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Exception Error{&lt;/pre&gt;&lt;pre&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; error; }&lt;/pre&gt;&lt;pre class="alt"&gt;    set { error = &lt;span class="kwrd"&gt;value&lt;/span&gt;;}}&lt;/pre&gt;&lt;pre&gt;  &lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Int64 ElapsedMilliseconds{&lt;/pre&gt;&lt;pre&gt;    get { &lt;span class="kwrd"&gt;lock&lt;/span&gt;(DownloadStopWatch) &lt;/pre&gt;&lt;pre&gt;            &lt;span class="kwrd"&gt;return&lt;/span&gt; DownloadStopWatch.ElapsedMilliseconds;}}&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; Int64 Length{&lt;/pre&gt;&lt;pre class="alt"&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; length; }&lt;/pre&gt;&lt;pre&gt;    set { length = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }}&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; String LocalDirectory{&lt;/pre&gt;&lt;pre class="alt"&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; localDirectory; }}&lt;/pre&gt;&lt;pre&gt;  &lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; String RemoteFile { &lt;/pre&gt;&lt;pre&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; remoteFile; } }&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; String FileName { &lt;/pre&gt;&lt;pre class="alt"&gt;    get { &lt;span class="kwrd"&gt;return&lt;/span&gt; fileName; } }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; Boolean disposing = &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; Stopwatch DownloadStopWatch = &lt;span class="kwrd"&gt;new&lt;/span&gt; Stopwatch();&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; Int32 BufferLength;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; NetworkCredential Credential;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; FtpWebRequest Ftpcon;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; Stream Target;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; Stream Source;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; Byte[] Buffer;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;internal&lt;/span&gt; FtpDownloadState(Int32 ID, String LocalDirectory, String RemoteFile, &lt;/pre&gt;&lt;pre class="alt"&gt;                            Int32 BufferLength, String Login, String Password)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.localDirectory = LocalDirectory;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.remoteFile = RemoteFile;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.BufferLength = BufferLength;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; ((Login!=&lt;span class="kwrd"&gt;null&lt;/span&gt;)&amp;amp;&amp;amp;(Login.Length&amp;gt;0))&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;this&lt;/span&gt;.Credential = &lt;span class="kwrd"&gt;new&lt;/span&gt; NetworkCredential(Login, Password);&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.fileName = Path.GetFileName(&lt;span class="kwrd"&gt;this&lt;/span&gt;.RemoteFile);&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.Buffer = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;byte&lt;/span&gt;[&lt;span class="kwrd"&gt;this&lt;/span&gt;.BufferLength];&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;this&lt;/span&gt;.id = ID;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt; ToString()&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (&lt;span class="kwrd"&gt;this&lt;/span&gt;.error == &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt;.Format(&lt;span class="str"&gt;"{0}: {1}kB/{2}kB en {3}s."&lt;/span&gt;, &lt;span class="kwrd"&gt;this&lt;/span&gt;.fileName,&lt;/pre&gt;&lt;pre&gt;        &lt;span class="kwrd"&gt;this&lt;/span&gt;.Donwloaded / 1024, &lt;span class="kwrd"&gt;this&lt;/span&gt;.Length / 1024,&lt;/pre&gt;&lt;pre class="alt"&gt;        &lt;span class="kwrd"&gt;this&lt;/span&gt;.ElapsedMilliseconds / 1000);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;string&lt;/span&gt;.Format(&lt;span class="str"&gt;"{0}: {1}"&lt;/span&gt;, &lt;span class="kwrd"&gt;this&lt;/span&gt;.fileName, &lt;span class="kwrd"&gt;this&lt;/span&gt;.error.Message);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Dispose(Boolean d)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (!disposing)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;this&lt;/span&gt;.Target.Dispose();&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;this&lt;/span&gt;.Source.Dispose();&lt;/pre&gt;&lt;pre class="alt"&gt;      GC.SuppressFinalize(&lt;span class="kwrd"&gt;this&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Dispose()&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    Dispose(&lt;span class="kwrd"&gt;true&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  ~FtpDownloadState()&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    Dispose(&lt;span class="kwrd"&gt;true&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Como se puede ver, esta clase no tiene funcionalidad FTP en si misma, lo que permite poder substituir la entidad ó la lógica de forma separada, incluso trabajar con otros tipos de Streams. &lt;/p&gt;
&lt;p&gt;Sobre la sincronización, se protege &lt;i&gt;ElapsedMillisecons&lt;/i&gt; y &lt;i&gt;Downloaded&lt;/i&gt; porque son dos&amp;nbsp;miembros de 64bits que pueden ser modificados al mismo tiempo que se leen, por lo que hay que asegurar la atomicidad.&lt;/p&gt;
&lt;p&gt;Ahora la lógica que realiza todo el proceso de la descarga de FTP. El principio de funcionamiento&amp;nbsp;es el mismo que el ejemplo de la &lt;a href="http://www.vtortola.net/post/Descargando-un-fichero-por-FTP.aspx"&gt;descarga FTP&lt;/a&gt;,&amp;nbsp;solo que ahora uso&amp;nbsp;.BeginRead en lugar de&amp;nbsp;.Read.&amp;nbsp;&amp;nbsp;Lamentablemente, para obtener cual es la longitud total del archivo hay que conectar en modo&amp;nbsp;&lt;i&gt;GetFileSize&lt;/i&gt; antes, lo cual complica un poco la lógica, que aún así es bastante simple:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; FtpAsyncDownload&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Evento de fin de descarga&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;event&lt;/span&gt; FtpEventDlg_ FtpDownloadEvent;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Orden de descarga&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Prepara el objectState y obtiene pide el tamaño.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;public&lt;/span&gt; IFtpDownloadInfo Download(Int32 ID, String LocalDirectory, &lt;/pre&gt;&lt;pre class="alt"&gt;                                          String RemoteFile,Int32 BufferLength, &lt;/pre&gt;&lt;pre&gt;                                          String Login, String Password)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    FtpDownloadState ftpdwn = &lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;new&lt;/span&gt; FtpDownloadState(ID, LocalDirectory, RemoteFile, BufferLength, &lt;/pre&gt;&lt;pre&gt;                           Login, Password);&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;/pre&gt;&lt;pre&gt;    ftpdwn.Ftpcon = (FtpWebRequest)FtpWebRequest.Create(ftpdwn.RemoteFile);&lt;/pre&gt;&lt;pre class="alt"&gt;    ftpdwn.Ftpcon.Credentials = ftpdwn.Credential;&lt;/pre&gt;&lt;pre&gt;    ftpdwn.Ftpcon.KeepAlive = &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;    ftpdwn.Ftpcon.UseBinary = &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;    ftpdwn.Ftpcon.Proxy = &lt;span class="kwrd"&gt;null&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;    ftpdwn.Ftpcon.EnableSsl = &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;    ftpdwn.Ftpcon.Method = WebRequestMethods.Ftp.GetFileSize;&lt;/pre&gt;&lt;pre class="alt"&gt;    ftpdwn.Source = ftpdwn.Ftpcon.GetResponse().GetResponseStream();&lt;/pre&gt;&lt;pre&gt;    ftpdwn.Source.BeginRead(ftpdwn.Buffer, 0,ftpdwn.BufferLength, &lt;/pre&gt;&lt;pre class="alt"&gt;                            startDownload, ftpdwn);&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;return&lt;/span&gt; ftpdwn;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Comienza la descarga&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Obtiene el tamaño y pide el inicio de &lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// la descarga del archivo.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; startDownload(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    FtpDownloadState ftpdwn = ia.AsyncState &lt;span class="kwrd"&gt;as&lt;/span&gt; FtpDownloadState;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Source.EndRead(ia);&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Length = ftpdwn.Ftpcon.GetResponse().ContentLength;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (ftpdwn.Length &amp;lt;= 0) &lt;span class="kwrd"&gt;throw&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; ArgumentException(&lt;span class="str"&gt;"FileSize &amp;lt;=0"&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      ftpdwn.Ftpcon = (FtpWebRequest)FtpWebRequest.Create(ftpdwn.RemoteFile);&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Ftpcon.Credentials = ftpdwn.Credential;&lt;/pre&gt;&lt;pre&gt;      ftpdwn.Ftpcon.KeepAlive = &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Ftpcon.UseBinary = &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;      ftpdwn.Ftpcon.Proxy = &lt;span class="kwrd"&gt;null&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Ftpcon.EnableSsl = &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;      ftpdwn.Ftpcon.Method = WebRequestMethods.Ftp.DownloadFile;&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Target = &lt;span class="kwrd"&gt;new&lt;/span&gt; FileStream(Path.Combine(ftpdwn.LocalDirectory, ftpdwn.FileName), &lt;/pre&gt;&lt;pre&gt;                                     FileMode.Create, FileAccess.Write, FileShare.None);&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Source = ftpdwn.Ftpcon.GetResponse().GetResponseStream();&lt;/pre&gt;&lt;pre&gt;      ftpdwn.DownloadStopWatch.Start();&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Source.BeginRead(ftpdwn.Buffer, 0, ftpdwn.BufferLength, &lt;/pre&gt;&lt;pre&gt;                                    downloadCallback, ftpdwn);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;catch&lt;/span&gt; (Exception ex)&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      ftpdwn.Error = ex;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (FtpDownloadEvent != &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;/pre&gt;&lt;pre&gt;        FtpDownloadEvent.Invoke(ftpdwn);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Descarga&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Función que se ejecuta cada vez que se obtiene&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// un buffer completo para escribir y volver a ejecutarse &lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// hasta que acabe.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; downloadCallback(IAsyncResult ia)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    FtpDownloadState ftpdwn = ia.AsyncState &lt;span class="kwrd"&gt;as&lt;/span&gt; FtpDownloadState;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;int&lt;/span&gt; readed = ftpdwn.Source.EndRead(ia);&lt;/pre&gt;&lt;pre&gt;      ftpdwn.Target.Write(ftpdwn.Buffer, 0, readed);&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Donwloaded += readed;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (!ftpdwn.Ended)&lt;/pre&gt;&lt;pre&gt;      {&lt;/pre&gt;&lt;pre class="alt"&gt;        ftpdwn.Source.BeginRead(ftpdwn.Buffer, 0, ftpdwn.BufferLength,&lt;/pre&gt;&lt;pre&gt;                                      downloadCallback, ftpdwn);&lt;/pre&gt;&lt;pre class="alt"&gt;      }&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;else&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      {&lt;/pre&gt;&lt;pre&gt;        &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;        {&lt;/pre&gt;&lt;pre&gt;          &lt;span class="kwrd"&gt;if&lt;/span&gt; (FtpDownloadEvent != &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;/pre&gt;&lt;pre class="alt"&gt;            FtpDownloadEvent.Invoke(ftpdwn);&lt;/pre&gt;&lt;pre&gt;        }&lt;/pre&gt;&lt;pre class="alt"&gt;        &lt;span class="kwrd"&gt;catch&lt;/span&gt; { }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;        ftpdwn.DownloadStopWatch.Stop();&lt;/pre&gt;&lt;pre&gt;        ftpdwn.Dispose();&lt;/pre&gt;&lt;pre class="alt"&gt;      }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;catch&lt;/span&gt; (Exception ex)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      ftpdwn.Error = ex;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (FtpDownloadEvent != &lt;span class="kwrd"&gt;null&lt;/span&gt;)&lt;/pre&gt;&lt;pre class="alt"&gt;        FtpDownloadEvent.Invoke(ftpdwn);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Básicamente el funcionamiento consiste en lo siguiente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se llama al método &lt;b&gt;Download&lt;/b&gt;. 
&lt;/li&gt;&lt;li&gt;Este crea un nuevo objeto de estado e invoca asíncronamente la obtención del tamaño del archivo. Indica que al finalizar vaya al método &lt;b&gt;startDownload&lt;/b&gt;. 
&lt;/li&gt;&lt;li&gt;Se&amp;nbsp;ejecuta &lt;b&gt;startDownload&lt;/b&gt;, obtiene el tamaño de archivo, configura la conexión para realizar la descarga&amp;nbsp;e invoca asíncronamente la descarga del archivo. Indica que al finalizar vaya al método &lt;b&gt;downloadCallback&lt;/b&gt;. 
&lt;/li&gt;&lt;li&gt;Cuando haya descargado un buffer completo, se&amp;nbsp;llama&amp;nbsp;a &lt;b&gt;downloadCallback&lt;/b&gt;, escribe el resultado en el archivo y si no se ha descargado todo aún se vuelve a invocar asíncronamente la descarga&amp;nbsp;hasta que complete. 
&lt;/li&gt;&lt;li&gt;Cuando acaba, dispara el evento y&amp;nbsp;llama a .Dispose para que libere los Streams del objeto de estado.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Para realizar una descarga, simplemente hay que subscribirse al evento e invocar el método estático Download con los parámetros adecuados:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;      FtpAsyncDownload.FtpDownloadEvent += &lt;/pre&gt;&lt;pre&gt;          &lt;span class="kwrd"&gt;new&lt;/span&gt; FtpEventDlg_(FtpAsyncDownload_FtpDownloadEvent);&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;/pre&gt;&lt;pre&gt;      FtpAsyncDownload.Download(0, Environment.CurrentDirectory, &lt;span class="str"&gt;@"ftp://miftp.com/miarchivo.rar"&lt;/span&gt;, &lt;/pre&gt;&lt;pre class="alt"&gt;                                      8192, &lt;span class="str"&gt;"MiLogin"&lt;/span&gt;, &lt;span class="str"&gt;"M1P455W0RD"&lt;/span&gt;);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Como decía anteriormente, el método Download devuelve un objeto de tipo IFtpDownloadInfo con el que podemos monitorizar el estado de la descarga. Una vez acabe, se disparará el evento si estamos subscritos a él.&lt;/p&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Cliente-FTP-asincrono.aspx"&gt;Cliente FTP asíncrono | vtortola.NET&lt;/a&gt;&lt;/h5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5566" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>Extendiendo un ProgressBar. FlatProgressBar.</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/06/5540.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/06/5540.aspx</id><published>2007-10-06T20:03:38Z</published><updated>2007-10-06T20:03:38Z</updated><content type="html">&lt;img height="137" alt="FlatProgressBar" src="http://elbruno.com/blogs/vtortola/WindowsLiveWriter/ExtendiendounProgressBar.FlatProgressBa_D0EE/FlatProgressBar_1.jpg" width="240" align="left" border="0"&gt;  &lt;p&gt;Vamos con una de &lt;em&gt;Arts Attack... &lt;/em&gt;El &lt;a href="http://msdn2.microsoft.com/es-es/library/system.windows.forms.progressbar(VS.80).aspx" target="_blank"&gt;ProgressBar&lt;/a&gt; de .NET es "bonito", sobre todo en Windows&amp;nbsp;Vista, pero a mi gusto prefería menos &lt;em&gt;destellos fashion&lt;/em&gt; y un poco más de información... y ya puestos ... un estilo &lt;em&gt;flat&lt;/em&gt; :D&amp;nbsp; &lt;/p&gt; &lt;p&gt;En vez de crear uno from scratch, mucho mejor extender el ProgressBar, añadirle nuevas propiedades y redefinir como repintarlo. Código fuente al final del artículo ;)&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;Entre los puntos fuertes de extender un control WinForm, esta el de redifinir los estilo con &lt;a href="http://msdn2.microsoft.com/es-es/library/system.windows.forms.control.setstyle(VS.80).aspx" target="_blank"&gt;Control.SetStyle&lt;/a&gt; y sobreescribir el método &lt;a href="http://msdn2.microsoft.com/es-es/library/system.windows.forms.control.onpaint(vs.80).aspx" target="_blank"&gt;OnPaint&lt;/a&gt;, dentro de este obtemenos el objeto &lt;a href="http://msdn2.microsoft.com/es-es/library/system.drawing.graphics(VS.80).aspx" target="_blank"&gt;Graphics&lt;/a&gt; con el que podemos pintar formas y textos. Mediante atributos configuramos los elementos que Visual Studio usará para mostrar sus propiedades:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/es-es/library/system.drawing.toolboxbitmapattribute(VS.80).aspx" target="_blank"&gt;ToolBoxBitmap&lt;/a&gt;: Sobre la clase, indica el icono &amp;nbsp;&lt;img height="16" alt="FlatProgressBar" src="http://elbruno.com/blogs/vtortola/WindowsLiveWriter/ExtendiendounProgressBar.FlatProgressBa_D0EE/FlatProgressBar_1.png" width="16" border="0"&gt; que mostrará Visual Studio en la paleta de componentes. Se añade una imagen .bmp al proyecto como recurso embebido, después se indica tal y como aparece en el código. A veces no funciona bien, así que hay un truco creando esa clase vacia 'resfinder' y luego indicando la imagen junto al Namespace, funciona siempre :D  &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/es-es/library/system.componentmodel.browsableattribute(VS.80).aspx" target="_blank"&gt;Browsable&lt;/a&gt;: Sobre una propiedad, indica si Visual Studio debe mostrarla en el Property Grid.  &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/es-es/library/system.componentmodel.categoryattribute(VS.80).aspx" target="_blank"&gt;CategoryAttribute&lt;/a&gt;: Sobre una propiedad, indica en que categoria del Property Grid colocarla.  &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/es-es/library/system.componentmodel.descriptionattribute(VS.80).aspx" target="_blank"&gt;DescriptionAttribute&lt;/a&gt;: Sobre una propiedad, indica la descripción de esa propiedad en el Property Grid.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;&lt;img height="150" alt="flatprogressbartool" src="http://elbruno.com/blogs/vtortola/WindowsLiveWriter/ExtendiendounProgressBar.FlatProgressBa_D0EE/flatprogressbartool_1.jpg" width="240" align="left" border="0"&gt; Una vez generado el componente en un ensamblado, para usarlo solo tenemos que añadirlo a la paleta de nuestro VS2005, desde el ToolBox, botón derecho, Choose Items, le damos a Browse para ir hasta el ensamblado, lo selecionamos y se añade a la paleta.&lt;/p&gt; &lt;p&gt;Asi mediante VS2005 podemos configurar los nuevos elementos de nuestro ProgressBar, además de ocultar otros que no nos interesan como se puede ver con por ejemplo la propiedad&amp;nbsp;&lt;em&gt;Style&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;&amp;nbsp; Pintar en un formulario es relativemente fácil con la clase Graphics y sus utilidades, pero requiere algo de paciencia y "prueba-error" para ajustarlo adecuadamente. Me apunto en el TODO escribir sobre las nociones básicas de GDI+.&lt;/p&gt; &lt;p&gt;Y ahora... el código fuente:&lt;/p&gt; &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Collections.Generic;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Text;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Windows.Forms;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Drawing;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Drawing.Drawing2D;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System.ComponentModel;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;internal&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; resfinder { }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;namespace&lt;/span&gt; CustomProgressBars&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  [ToolboxBitmap(&lt;span class="kwrd"&gt;typeof&lt;/span&gt;(resfinder), &lt;span class="str"&gt;"FlatProgressBar.FlatProgressBar.bmp"&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;class&lt;/span&gt; FlatProgressBar : ProgressBar&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; Pen _border;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; String _text;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; SolidBrush _brushForeColor;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; SolidBrush _brushBarColor;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    [Browsable(&lt;span class="kwrd"&gt;true&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    [CategoryAttribute(&lt;span class="str"&gt;"FlatProgressBar"&lt;/span&gt;),&lt;/pre&gt;&lt;pre class="alt"&gt;    DescriptionAttribute(&lt;span class="str"&gt;"Color de la Barra"&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; Color BarColor&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; _brushBarColor.Color; }&lt;/pre&gt;&lt;pre class="alt"&gt;      set { _brushBarColor.Color = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    [Browsable(&lt;span class="kwrd"&gt;true&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    [CategoryAttribute(&lt;span class="str"&gt;"FlatProgressBar"&lt;/span&gt;),&lt;/pre&gt;&lt;pre&gt;    DescriptionAttribute(&lt;span class="str"&gt;"Color del Texto"&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; Color TextColor&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; _brushForeColor.Color; }&lt;/pre&gt;&lt;pre&gt;      set { _brushForeColor.Color = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    [Browsable(&lt;span class="kwrd"&gt;true&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    [CategoryAttribute(&lt;span class="str"&gt;"FlatProgressBar"&lt;/span&gt;),&lt;/pre&gt;&lt;pre class="alt"&gt;    DescriptionAttribute(&lt;span class="str"&gt;"Color del Borde"&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; Color BorderColor&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; _border.Color; }&lt;/pre&gt;&lt;pre class="alt"&gt;      set { _border.Color = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    [Browsable(&lt;span class="kwrd"&gt;true&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    [CategoryAttribute(&lt;span class="str"&gt;"FlatProgressBar"&lt;/span&gt;),&lt;/pre&gt;&lt;pre&gt;    DescriptionAttribute(&lt;span class="str"&gt;"Color del Borde"&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; Single BorderWidth&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; _border.Width; }&lt;/pre&gt;&lt;pre&gt;      set { _border.Width = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    [Browsable(&lt;span class="kwrd"&gt;true&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    [CategoryAttribute(&lt;span class="str"&gt;"FlatProgressBar"&lt;/span&gt;),&lt;/pre&gt;&lt;pre class="alt"&gt;    DescriptionAttribute(&lt;span class="str"&gt;"Color de Fondo"&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; Color BackColor&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;base&lt;/span&gt;.BackColor; }&lt;/pre&gt;&lt;pre class="alt"&gt;      set { &lt;span class="kwrd"&gt;base&lt;/span&gt;.BackColor = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    [Browsable(&lt;span class="kwrd"&gt;true&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    [CategoryAttribute(&lt;span class="str"&gt;"FlatProgressBar"&lt;/span&gt;),&lt;/pre&gt;&lt;pre&gt;    DescriptionAttribute(&lt;span class="str"&gt;"Texto de la barra"&lt;/span&gt;),&lt;/pre&gt;&lt;pre class="alt"&gt;    DefaultValue(&lt;span class="str"&gt;"Text"&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; String Text&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; _text; }&lt;/pre&gt;&lt;pre class="alt"&gt;      set { _text = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    [Browsable(&lt;span class="kwrd"&gt;true&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    [CategoryAttribute(&lt;span class="str"&gt;"FlatProgressBar"&lt;/span&gt;),&lt;/pre&gt;&lt;pre&gt;    DescriptionAttribute(&lt;span class="str"&gt;"Tipo de fuente del texto."&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; &lt;span class="kwrd"&gt;new&lt;/span&gt; Font Font&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;base&lt;/span&gt;.Font; }&lt;/pre&gt;&lt;pre&gt;      set { &lt;span class="kwrd"&gt;base&lt;/span&gt;.Font = &lt;span class="kwrd"&gt;value&lt;/span&gt;; }&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    [Browsable(&lt;span class="kwrd"&gt;false&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;public&lt;/span&gt; ProgressBarStyle Style&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; ProgressBarStyle.Continuous; }&lt;/pre&gt;&lt;pre class="alt"&gt;      set { }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    [Browsable(&lt;span class="kwrd"&gt;false&lt;/span&gt;)]&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;public&lt;/span&gt; Int32 MarqueeAnimationSpeed&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; 0; }&lt;/pre&gt;&lt;pre&gt;      set { }&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    [Browsable(&lt;span class="kwrd"&gt;false&lt;/span&gt;)]&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;public&lt;/span&gt; Color ForeColor&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      get { &lt;span class="kwrd"&gt;return&lt;/span&gt; TextColor; }&lt;/pre&gt;&lt;pre class="alt"&gt;      set { }&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;public&lt;/span&gt; FlatProgressBar()&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      InitializeComponent();&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; InitializeComponent()&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;this&lt;/span&gt;.SetStyle(ControlStyles.AllPaintingInWmPaint |&lt;/pre&gt;&lt;pre&gt;      ControlStyles.UserPaint |&lt;/pre&gt;&lt;pre class="alt"&gt;      ControlStyles.OptimizedDoubleBuffer |&lt;/pre&gt;&lt;pre&gt;      ControlStyles.ResizeRedraw |&lt;/pre&gt;&lt;pre class="alt"&gt;      ControlStyles.SupportsTransparentBackColor, &lt;span class="kwrd"&gt;true&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      _border = &lt;span class="kwrd"&gt;new&lt;/span&gt; Pen(Brushes.Black);&lt;/pre&gt;&lt;pre&gt;      _brushForeColor = &lt;span class="kwrd"&gt;new&lt;/span&gt; SolidBrush(Color.White);&lt;/pre&gt;&lt;pre class="alt"&gt;      _brushBarColor = &lt;span class="kwrd"&gt;new&lt;/span&gt; SolidBrush(Color.Gray);&lt;/pre&gt;&lt;pre&gt;      &lt;span class="kwrd"&gt;base&lt;/span&gt;.Font = &lt;span class="kwrd"&gt;new&lt;/span&gt; Font(&lt;span class="str"&gt;"Tahoma"&lt;/span&gt;, 10, FontStyle.Bold);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;protected&lt;/span&gt; &lt;span class="kwrd"&gt;override&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; OnPaint(PaintEventArgs e)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// cuando mide cada paso&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      Single step = (Single)&lt;span class="kwrd"&gt;this&lt;/span&gt;.Width / &lt;span class="kwrd"&gt;this&lt;/span&gt;.Maximum;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// cuantos puntos es el progreso actual&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      Single progressInPoints = (&lt;span class="kwrd"&gt;this&lt;/span&gt;.Value * step);&lt;/pre&gt;&lt;pre&gt;      progressInPoints = progressInPoints &amp;gt; 0 ? progressInPoints : 0;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// barra de progreso&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      e.Graphics.FillRectangle(_brushBarColor, 0, 0, progressInPoints, &lt;span class="kwrd"&gt;this&lt;/span&gt;.Height);&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// texto con antialias&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      SmoothingMode bk = e.Graphics.SmoothingMode;&lt;/pre&gt;&lt;pre class="alt"&gt;      e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// escribo el texto&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      e.Graphics.DrawString(_text, Font, _brushForeColor,&lt;/pre&gt;&lt;pre class="alt"&gt;                  &lt;span class="kwrd"&gt;new&lt;/span&gt; PointF(6, (&lt;span class="kwrd"&gt;this&lt;/span&gt;.Height - &lt;span class="kwrd"&gt;this&lt;/span&gt;.Font.Height) / 2));&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;string&lt;/span&gt; percent = ((&lt;span class="kwrd"&gt;this&lt;/span&gt;.Maximum / 100) * &lt;span class="kwrd"&gt;this&lt;/span&gt;.Value).ToString() + &lt;span class="str"&gt;'%'&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;      percent = percent.PadLeft(4, &lt;span class="str"&gt;' '&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// mido el porcentaje en puntos para saber&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// donde ubicarlo &lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      SizeF swidth = e.Graphics.MeasureString(percent, &lt;span class="kwrd"&gt;base&lt;/span&gt;.Font);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// escribo porcentaje&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      e.Graphics.DrawString(percent, &lt;span class="kwrd"&gt;base&lt;/span&gt;.Font, _brushForeColor,&lt;/pre&gt;&lt;pre&gt;                  &lt;span class="kwrd"&gt;new&lt;/span&gt; PointF((&lt;span class="kwrd"&gt;this&lt;/span&gt;.Width - _border.Width - swidth.Width - 1),&lt;/pre&gt;&lt;pre class="alt"&gt;                             (&lt;span class="kwrd"&gt;this&lt;/span&gt;.Height - &lt;span class="kwrd"&gt;base&lt;/span&gt;.Font.Height) / 2));&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// restauro el modo&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      e.Graphics.SmoothingMode = bk;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// dibujo el border si es mayor que 0&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="kwrd"&gt;if&lt;/span&gt; (_border.Width &amp;gt;= 1)&lt;/pre&gt;&lt;pre&gt;        e.Graphics.DrawRectangle(_border, 0, 0, &lt;span class="kwrd"&gt;this&lt;/span&gt;.Width - 1, &lt;span class="kwrd"&gt;this&lt;/span&gt;.Height - 1);&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Extendiendo-un-ProgressBar-FlatProgressBar.aspx"&gt;Extendiendo un ProgressBar. FlatProgressBar. | vtortola.NET&lt;/a&gt;&lt;/h5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5540" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>Optimizaci&amp;amp;#243;n de la evaluaci&amp;amp;#243;n de condiciones</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/05/5516.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/05/5516.aspx</id><published>2007-10-06T02:54:42Z</published><updated>2007-10-06T02:54:42Z</updated><content type="html">&lt;p&gt;En C#, el orden de evaluación de &lt;a href="http://msdn2.microsoft.com/es-es/library/6a71f45d(VS.80).aspx" target="_blank"&gt;operadores&lt;/a&gt; es de &lt;a href="http://msdn2.microsoft.com/en-us/library/Aa691322(VS.71).aspx" target="_blank"&gt;izquierda a derecha&lt;/a&gt; por orden de prioridad&amp;nbsp;en cualquier tipo de operación. Además, a la hora de evaluar condiciones lógicas&amp;nbsp;se puede anticipar el resultado final&amp;nbsp;si el resultado parcial hasta el momento es inamovible, es decir, que pase lo que pase en el resto de condiciones&amp;nbsp;el resultado parcial será el definitivo, en ese caso, el resto de condiciones no son evaluadas. &lt;/p&gt; &lt;p&gt;Desde el punto de vista del rendimiento, nos permite ahorrar evaluaciones (y con ello instrucciones)&amp;nbsp;innecesarias en nuestra lógica si nos aprovechamos bien de esta característica.&lt;/p&gt; &lt;p&gt;Para ejemplificar este comportamiento, observa este simple programa:&lt;/p&gt; &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Main(&lt;span class="kwrd"&gt;string&lt;/span&gt;[] args)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;if&lt;/span&gt; (Test1(&lt;span class="kwrd"&gt;false&lt;/span&gt;) || Test2(&lt;span class="kwrd"&gt;false&lt;/span&gt;) || Test3(&lt;span class="kwrd"&gt;false&lt;/span&gt;))&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    Console.WriteLine(&lt;span class="str"&gt;"Dentro!"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  Console.ReadKey();&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; Boolean Test1(Boolean param)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  Console.WriteLine(&lt;span class="str"&gt;"Test1"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;return&lt;/span&gt; param ^ &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; Boolean Test2(Boolean param)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  Console.WriteLine(&lt;span class="str"&gt;"Test2"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;return&lt;/span&gt; param ^ &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; Boolean Test3(Boolean param)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  Console.WriteLine(&lt;span class="str"&gt;"Test3"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;return&lt;/span&gt; param ^ &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;La instrucción 'if' evalua una condición formada por&amp;nbsp;dos operaciones OR sobre&amp;nbsp;tres métodos que devuelven &lt;em&gt;true&lt;/em&gt; ó &lt;em&gt;false&lt;/em&gt;. Al devolver 'Test1' un &lt;em&gt;true&lt;/em&gt; y siendo una opearción OR&amp;nbsp;sea cual sea el resultado de 'Test2' y 'Test3'&amp;nbsp;... el resultado final será &lt;em&gt;true&lt;/em&gt;, por lo que estos dos últimos no son evaluados. El programa devuelve la siguiente salida:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test1&lt;br&gt;Dentro!&lt;/strong&gt; 
&lt;p&gt;Sin embargo, si modificamos la línea del 'if' por:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;if&lt;/span&gt; (Test1(&lt;span class="kwrd"&gt;true&lt;/span&gt;) || Test2(&lt;span class="kwrd"&gt;false&lt;/span&gt;) || Test3(&lt;span class="kwrd"&gt;false&lt;/span&gt;))&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Test1&lt;br&gt;Test2&lt;br&gt;Dentro!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;El resultado varia, ya que al devolver 'Test1' false, el resultado final no se puede anticipar, pero cuando 'Test2' devuelve true, ya no es necesario evaluar 'Test3':&lt;/p&gt;
&lt;p&gt;Por ejemplo si sustituimos los OR por AND, el resultado es distinto ... pero siguiendo la misma línea de actuación:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;if&lt;/span&gt; (Test1(&lt;span class="kwrd"&gt;false&lt;/span&gt;) &amp;amp;&amp;amp; Test2(&lt;span class="kwrd"&gt;false&lt;/span&gt;) &amp;amp;&amp;amp; Test3(&lt;span class="kwrd"&gt;false&lt;/span&gt;))&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Test1&lt;br&gt;Test2&lt;br&gt;Test3&lt;br&gt;Dentro!&lt;/strong&gt; 
&lt;p&gt;Al ser las dos primeras condiciones true, cualquier resultado parcial podría ser anulado por un false en 'Test3', por lo que debe evaluar las 3ª.&lt;/p&gt;
&lt;p&gt;Este comportamiento aplica a cualquier tipo de operación booleana, por compleja que sea, si en un determinado momento dada la&amp;nbsp;ecuación lógica,&amp;nbsp;se sabe que el resultado parcial será definitivo... el resto se obvia.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;La última sentencia 'if' probada, podría ser fácilmente substituible por condiciones anidadas ya que al igual que la operación con AND, evaluar una condición implica que se ha superado la anterior:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;if&lt;/span&gt; (Test1(&lt;span class="kwrd"&gt;false&lt;/span&gt;))&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;if&lt;/span&gt; (Test2(&lt;span class="kwrd"&gt;false&lt;/span&gt;))&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;if&lt;/span&gt; (Test3(&lt;span class="kwrd"&gt;false&lt;/span&gt;))&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      Console.WriteLine(&lt;span class="str"&gt;"Dentro!"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pero en el resto de operaciones implica estructuras más complejas, que al igual que esta última (por simple que sea) deben ser evitadas siempre que no haya que emprender aluguna acción por cada condición a evaluar ó sea un requerimiento evaluarlas todas.&lt;/p&gt;
&lt;p&gt;Las estructuras condicionales desmesuradas y mal estructuradas, no solo implican un código ilegible y niveles de identación inaceptables, si no que una mala estructuración puede derivar en la evaluación redundante e innecesaria de condiciones. Evaluar una condición simple como&amp;nbsp;el valor de una variable puede ser&amp;nbsp;algo insignificante, pero si la evaluación es&amp;nbsp;directamente resultado de un método... ejecutar dicho método ó no puede significar una diferencia de rendimiento&amp;nbsp;proporcional al tiempo de CPU que lleva ejecutarlo. Evita la lógica innecesaria !!&lt;/p&gt;
&lt;p&gt;Una vez tengamos claro en flujo lógico de nuestra aplicación, y si como decía las condiciones se evaluan pero no se hace nada por cada una de ellas, podemos simplificar fácilmente&amp;nbsp;pequeños grupos de&amp;nbsp;evaluaciones mediante las &lt;a href="http://www.monografias.com/trabajos14/karnaughmapa/karnaughmapa.shtml"&gt;tablas de Karnaugh&lt;/a&gt;, y ecuaciones ya más grandes mediante el&amp;nbsp;&lt;a href="http://www.monografias.com/trabajos14/algebra-booleana/algebra-booleana.shtml#al"&gt;algrebra de Boole&lt;/a&gt;, muy muy útil para simplificar&amp;nbsp;bloques de&amp;nbsp;&lt;em&gt;lógica de negocios&lt;/em&gt;.&amp;nbsp;Dos cosas de las que escribiré en mi blog en breve...&lt;/p&gt;
&lt;p&gt;Como colofón, cuando evaluemos condiciones que pueden tomar muchos valores, mejor que la sucesión continua de &lt;a href="http://msdn2.microsoft.com/es-es/library/5011f09h(VS.80).aspx" target="_blank"&gt;else if&lt;/a&gt;, es mejor usar la instrucción &lt;a href="http://msdn2.microsoft.com/es-es/library/06tc147t(VS.80).aspx" target="_blank"&gt;switch&lt;/a&gt;. Como muestra un sencillo benchmark (que llevaba tiempo sin hacer uno xD):&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;Stopwatch sw = &lt;span class="kwrd"&gt;new&lt;/span&gt; Stopwatch();&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;sw.Start();&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;for&lt;/span&gt;(&lt;span class="kwrd"&gt;int&lt;/span&gt; i =0;i&amp;lt;1000000;i++)&lt;/pre&gt;&lt;pre class="alt"&gt;  TestCharIfElse(&lt;span class="str"&gt;'z'&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;sw.Stop();&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;Console.WriteLine(&lt;span class="str"&gt;"TestCharIfElse: {0} ms."&lt;/span&gt;,sw.ElapsedMilliseconds);&lt;/pre&gt;&lt;pre class="alt"&gt;sw.Reset();&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;sw.Start();&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;for&lt;/span&gt; (&lt;span class="kwrd"&gt;int&lt;/span&gt; i = 0; i &amp;lt; 1000000; i++)&lt;/pre&gt;&lt;pre class="alt"&gt;  TestCharSwitch(&lt;span class="str"&gt;'z'&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;sw.Stop();&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;Console.WriteLine(&lt;span class="str"&gt;"TestCharSwitch: {0} ms."&lt;/span&gt;, sw.ElapsedMilliseconds);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Por un lado, 'TestCharIfElse' que com prueba si un caracter está en el alfabeto por medio de instruciones 'if else':&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; Boolean TestCharIfElse(Char c)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;if&lt;/span&gt; (c == &lt;span class="str"&gt;'a'&lt;/span&gt;)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;else&lt;/span&gt; &lt;span class="kwrd"&gt;if&lt;/span&gt; (c == &lt;span class="str"&gt;'b'&lt;/span&gt;)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;  }&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;/pre&gt;&lt;pre&gt;   &lt;strong&gt; [... Omitido por brevedad...]&lt;/strong&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;else&lt;/span&gt; &lt;span class="kwrd"&gt;if&lt;/span&gt; (c == &lt;span class="str"&gt;'z'&lt;/span&gt;)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;else&lt;/span&gt; &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Por otro, 'TestCharSwitch', que realiza la misma comprobación pero con la instrucción switch:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; Boolean TestCharSwitch(Char c)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;switch&lt;/span&gt; (c)&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;'a'&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;'b'&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;strong&gt;    [... Omitido por brevedad ...]&lt;/strong&gt;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;case&lt;/span&gt; &lt;span class="str"&gt;'z'&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;true&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;default&lt;/span&gt;: &lt;span class="kwrd"&gt;return&lt;/span&gt; &lt;span class="kwrd"&gt;false&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;La prueba envia un millón de veces el caracter 'z', que obliga a 'TestCharElseIf' a recorrer todas sus condiciones, mientras que 'TestCharSwitch' lo encuentra de forma más rápida como muestra el resultado:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TestCharIfElse: 57 ms.&lt;br&gt;TestCharSwitch: 12 ms.&lt;/strong&gt; 
&lt;p&gt;Casi 5 veces más rápido en este caso.&lt;/p&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Optimizacion-de-la-evaluacion-de-condiciones.aspx"&gt;Optimización de la evaluación de condiciones | vtortola.NET&lt;/a&gt;&lt;/h5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5516" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>CLR String Interning</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/04/5506.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/04/5506.aspx</id><published>2007-10-05T02:31:55Z</published><updated>2007-10-05T02:31:55Z</updated><content type="html">&lt;p&gt;Un String, es un tipo de referencia especial llamado inmutable, que quiere decir que el dato almacenado en el &lt;em&gt;heap&lt;/em&gt; no se puede cambiar, cuando asignamos un nuevo valor a una variable de este tipo, un nuevo dato es generado en otra posición del heap y su dirección de memoria asignada a dicha variable, el dato anterior queda listo para ser &lt;em&gt;GarbageCollected&lt;/em&gt;. Bien, pues este comportamiento que ... a unos les gusta más... a otros menos... se ideó así para evitar condiciones de anticipación en entornos multithreading tanto por motivos de consistencia como&amp;nbsp;de seguridad, y tiene la evidente desventaja del engorro de memoria y trabajo extra para el GC que provoca.&lt;/p&gt; &lt;p&gt;El &lt;strong&gt;String Interning&lt;/strong&gt; es una técnica de optimización que aplica el &lt;a href="http://www.vtortola.net/post/Modelo-de-ejecucion-del-NET-Framework-El-CLR.aspx"&gt;CLR&lt;/a&gt; sobre los String, que consiste en que los literales de este tipo&amp;nbsp;de nuestro dominio de aplicación, son almacenados en un HashTable interno, de forma que si dos variables tienen el mismo valor y este ha sido internado, ambas variables apuntan a la misma referencia. Esto es facilmente comprobable con este simple código:&lt;/p&gt; &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;String s1 = &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;String s2 = &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;Console.WriteLine(Object.ReferenceEquals(s1, s2)); &lt;span class="rem"&gt;// True&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ó por ejemplo:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;String s1 = &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(Object.ReferenceEquals(s1, &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;)); // True&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Este proceso de internamiento se lleva a cabo por el JIT de forma dinámica, y como decía, solo sobre los literales, si el String es el resultado de una operación no es internado:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;String s1 = &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;StringBuilder sb1 = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder(&lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;Console.WriteLine(Object.ReferenceEquals(s1, sb1.ToString())); &lt;span class="rem"&gt;// False&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Igual pasa con el resto de operaciones...&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;String s1 = &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(Object.ReferenceEquals(s1, &lt;span class="str"&gt;"Darker than"&lt;/span&gt;+&lt;span class="str"&gt;" BLACK"&lt;/span&gt;)); // True&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oh Wait! Que sucede aquí? Pues que el&amp;nbsp;CLR como siempre, optimizando al máximo, entiende esa concatenación de literales como un literal tal cual porque inevitablemente va a resultar en dicho dato jejeje,&amp;nbsp;... un poco más dificil:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;String s1 = &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;String s2 = String.Empty;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;foreach&lt;/span&gt; (&lt;span class="kwrd"&gt;char&lt;/span&gt; c &lt;span class="kwrd"&gt;in&lt;/span&gt; s1) s2 += c;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(Object.ReferenceEquals(s1, s2)); // False&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Podemos insertar directamente un String en la tabla de internados mediante el método &lt;a href="http://msdn2.microsoft.com/es-es/library/system.string.intern(VS.80).aspx"&gt;String.Intern&lt;/a&gt;&amp;nbsp;que nos devolverá un String apuntando al dato internado, si no existiese&amp;nbsp;lo crea:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;String s1 = &lt;span class="str"&gt;"Darker than BLACK"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;String s2 = String.Empty;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;foreach&lt;/span&gt; (&lt;span class="kwrd"&gt;char&lt;/span&gt; c &lt;span class="kwrd"&gt;in&lt;/span&gt; s1) s2 += c;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(Object.ReferenceEquals(s1, s2)); &lt;span class="rem"&gt;// False&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;s2 = String.Intern(s2);&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(Object.ReferenceEquals(s1, s2)); // True&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cabe destacar, que los String que añadamos a dicha tabla, no pueden ser recolectados por el GC hasta que se descargue el AppDomain, con lo que indica que habrá objetos &lt;em&gt;inmortales&lt;/em&gt; en el heap hasta entonces ocupando memoria. &lt;/p&gt;
&lt;p&gt;Además, añadir elementos a la tabla de internados también tiene su coste en rendimiento, pero un uso intencionado de esta característica puede mejorar mucho el rendimiento si se aprovecha bien, ya que podriamos realizar comparaciones de Strings con &lt;a href="http://msdn2.microsoft.com/en-us/library/system.object.referenceequals(vs.80).aspx"&gt;Object.ReferenceEquals&lt;/a&gt;&amp;nbsp;en lugar de &lt;a href="http://msdn2.microsoft.com/en-us/library/system.string.equals.aspx"&gt;String.Equals&lt;/a&gt;(==), ya que el primero es más rápido al simplemente comparar las referencias, mientras que el segundo primero para saberlo&amp;nbsp;evalua el número de caracteres y si coincide también caracter a caracter, pero claro, esto en el caso de que trabajemos siempre con los mismos String y esten todos internados :P&lt;/p&gt;
&lt;p&gt;También podemos simplemente consultar si un String ha sido internado con el método &lt;a href="http://msdn2.microsoft.com/en-us/library/system.string.isinterned(vs.80).aspx"&gt;String.IsInterned&lt;/a&gt;, que devuelve el String internado en caso de que sí, y nulo en caso de que no:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;StringBuilder sb1 = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder();&lt;/pre&gt;&lt;pre&gt;sb1.AppendFormat(&lt;span class="str"&gt;"{0} is {1}"&lt;/span&gt;, &lt;span class="str"&gt;"Hei"&lt;/span&gt;, &lt;span class="str"&gt;"BK201"&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;Console.WriteLine(String.IsInterned(sb1.ToString())==&lt;span class="kwrd"&gt;null&lt;/span&gt;); &lt;span class="rem"&gt;// True&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(String.IsInterned(&lt;span class="str"&gt;"BK201"&lt;/span&gt;) == &lt;span class="kwrd"&gt;null&lt;/span&gt;); // False&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;En el CLR 2.0, este comportamiento es por defecto, y aunque existe el atributo &lt;a href="http://msdn2.microsoft.com/en-us/library/system.runtime.compilerservices.compilationrelaxationsattribute(vs.80).aspx"&gt;CompilationRelaxations&lt;/a&gt; que permitiria anularlo ... el CLR &lt;strike&gt;se caga en él&lt;/strike&gt; lo ignora. En cualqueir caso no se debe desarrollar dando por hecho este comportamiento ya que en futuras versiones del CLR podría cambiar.&lt;/p&gt;
&lt;p&gt;Ventaja? Desventaja? Verdaderamente es algo engorroso. Por un lado se suele recomendar no trabajar nunca con String &lt;em&gt;harcodeados (literales)&lt;/em&gt;&amp;nbsp;en nuestro código, y por otro siempre que se trabaja con Strings se&amp;nbsp;suele realizar operaciones con ellas por lo que es dificil asegurar que un&amp;nbsp;String a evaluar este internado. Aunque es cierto que como comentaba antes podamos sacar partido a esta funcionalidad en algunas ocasiones contadas, por ejemplo si obtenemos de una BD una serie de cadenas de texto que no van a ser alteradas durante la ejecución y solo se utilizan como referencia de comparación ... podemos sacar partido a Object.ReferenceEquals para su evaluación.&lt;/p&gt;
&lt;p&gt;Y después de este largo post, que no tiene&amp;nbsp;más utilidad&amp;nbsp;que ser una curiosidad insana del CLR, aún te quieres devanar los sesos un poco más ... realmente ... ¿el método String.IsInterned funciona bien? &lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;String s1 = &lt;span class="str"&gt;"Black Shinigami"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;StringBuilder sb = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder(&lt;span class="str"&gt;"Black Shinigami"&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;String s2 = sb.ToString();&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(String.IsInterned(s1) != &lt;span class="kwrd"&gt;null&lt;/span&gt;); &lt;span class="rem"&gt;// True&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;Console.WriteLine(String.IsInterned(s2) != &lt;span class="kwrd"&gt;null&lt;/span&gt;); &lt;span class="rem"&gt;// True&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;Console.WriteLine(Object.ReferenceEquals(s1, s2)); // False&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Según este código, el CLR entiende que s1 y s2 estan internados, pero realmente solo lo esta s1, aunque s2 contiene un dato internado ... la posición del heap a la que apunta dicha variable no es la que esta internada ... ¿que sucede? Pues ya que en los foros del MSDN &lt;strike&gt;no saben&lt;/strike&gt; &lt;a href="http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2227273&amp;amp;SiteID=1"&gt;no entienden mi inglés&lt;/a&gt; xD ...&amp;nbsp;la solución me la ha dado el fabuloso libro &lt;a href="http://www.microsoft.com/MSPress/books/6522.aspx"&gt;CLR via C#&lt;/a&gt;, donde se explica que el HashTable de las String internadas, usa el dato como &lt;em&gt;key&lt;/em&gt; y la dirección de memoria del dato internado como valor,&amp;nbsp;lo que explica que&amp;nbsp;el CLR busca el dato contenido en s2 como clave&amp;nbsp;en el HashTable y obtiene la dirección de memoria en el &lt;em&gt;heap&lt;/em&gt; de s1, que es la que devuelve exactamente ... sabiendo esto podemos usar un método mejor:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;static&lt;/span&gt; Boolean IsThisInterned(String str)&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="kwrd"&gt;return&lt;/span&gt; Object.ReferenceEquals(String.IsInterned(str), str);&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ya finalizando, aclarar que el &lt;strong&gt;String Pooling&lt;/strong&gt; es una técnica similar pero&amp;nbsp;distinta, que aplica el compilador a las Strings cuando genera los metadatos, de forma que si hay varias Strings literales&amp;nbsp;con el mismo dato en el código fuetne, se introduce una sola vez en los metadatos y todas apuntan a esta. De esta forma se reduce el tamaño final del ensamblado. Esto es algo que ya hacían de forma parecida los compiladores de C/C++.&lt;/p&gt;
&lt;p&gt;Como nota final diré que si, que me encanta&amp;nbsp;de &lt;a href="http://es.wikipedia.org/wiki/Darker_than_Black"&gt;Darker Than BLACK&lt;/a&gt;&amp;nbsp;:D, de hecho&amp;nbsp;me estoy&amp;nbsp;descargando ahora mismo el último episodio, algo triste pero impacientemente esperado a la vez.&lt;/p&gt;
&lt;p&gt;Hasta otro capítulo de cosas que valen para poco.&lt;/p&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/CLR-String-Interning.aspx"&gt;CLR String Interning | vtortola.NET&lt;/a&gt;&lt;/h5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5506" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>Se publicar&amp;amp;#225;n las fuentes de la BCL de .NET</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/04/5501.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/04/5501.aspx</id><published>2007-10-04T12:53:00Z</published><updated>2007-10-04T12:53:00Z</updated><content type="html">&lt;P&gt;Gran noticia que me llega a través del correo de Avanade, &lt;A href="http://weblogs.asp.net/scottgu/archive/2007/10/03/releasing-the-source-code-for-the-net-framework-libraries.aspx" target=_blank&gt;Releasing the source code for the .NET Framework Libraries&lt;/A&gt; :D &lt;/P&gt;
&lt;P&gt;Iran incluidas en la salida de .NET 3.5 y VS2008, aunque es posible que esten disponibles para descarga en breve. Se publican bajo la licencia opensource de Microsoft &lt;A href="http://www.microsoft.com/resources/sharedsource/licensingbasics/referencelicense.mspx"&gt;MS-RL&lt;/A&gt;. &lt;/P&gt;
&lt;P&gt;Se comenzará con la publicación de: &lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;NET Base Class Libraries &lt;/DIV&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;System &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.IO &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Collections &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Configuration &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Threading &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Net &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Security &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Runtime &lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Text &lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;ASP.NET &lt;/DIV&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Web &lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;Windows Forms &lt;/DIV&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Windows.Forms &lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;ADO.NET &lt;/DIV&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Data &lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;XML &lt;/DIV&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Xml &lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;WPF &lt;/DIV&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;System.Windows &lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;En el artículo se ven instrucciones para integrar&amp;nbsp;el depurado con VS2008 de forma que se pueda seguir la pila de llamadas a través de estas fuentes. &lt;/P&gt;
&lt;P&gt;&lt;EM&gt;"Tener acceso al código fuente&amp;nbsp;e integración con el depurador de las librerias del .NET Framework será realmente valioso para los desarrolladores&amp;nbsp;en .NET. Ser capaz de&amp;nbsp;avanzar instrucción a instrucción y revisar el código fuente&amp;nbsp;&amp;nbsp;proporciona una mejor idea de como las librerias están implementadas y permite a los desarrolladores construir mejores aplicaciones e incluso hacer un mejor uso de&amp;nbsp;dichas librerias."&lt;/EM&gt; &lt;/P&gt;
&lt;P&gt;Más info también&amp;nbsp;en el blog de &lt;A href="http://blogs.msdn.com/sburke/" target=_blank&gt;Shawn Burke&lt;/A&gt;. &lt;/P&gt;
&lt;H5&gt;
&lt;P&gt;&lt;A class="" href="http://www.vtortola.net/post/Se-publican-las-fuentes-de-la-BCL-de-NET.aspx"&gt;Se publicarán las fuentes de la BCL de .NET&amp;nbsp;| vtortola.NET&lt;/A&gt;&lt;/P&gt;&lt;/H5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5501" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>Medir la velocidad de un Stream</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/28/5457.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/28/5457.aspx</id><published>2007-09-28T22:42:46Z</published><updated>2007-09-28T22:42:46Z</updated><content type="html">&lt;p&gt;Cosilla curiosa que nunca había hecho antes, medir la velocidad de lectura de un Stream, por ejemplo para saber a que velocidad en Bytes/s&amp;nbsp;descargamos de un &lt;a href="http://msdn2.microsoft.com/es-es/library/system.net.sockets.networkstream(vs.80).aspx"&gt;NetworkStream&lt;/a&gt;, ó de un FtpDataStream, ó para saber como de rápido lee nuestro disco duro ... ya sea con fines meramente estadísticos ó para encontrar el tamaño de buffer adecuado ... Es un procedmiento muy sencillo, se trata de dividir los bytes descargados entre los milisegundos transcurridos (cuantos bytes por milisegundo)&amp;nbsp;y multiplicarlo por 1000 (un segundo). Para ello nos apoyamos en la clase para realizar mediciones de tiempo con precisión, &lt;a href="http://msdn2.microsoft.com/es-es/library/System.Diagnostics.Stopwatch.aspx"&gt;StopWatch&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;En este ejemplo de una &lt;a href="http://www.vtortola.net/post/Descargando-un-fichero-por-FTP.aspx"&gt;descarga FTP&lt;/a&gt;, tenemos un FtpDataStream de donde leemos los datos y un FileStream donde los guardamos. Se podría hacer con practicamente cualquier&amp;nbsp;Stream.&amp;nbsp;Adicionalmente declaramos una variable global a la clase&amp;nbsp;Int64 donde almacenar la cantidad de Bytes leidos y un StopWatch global también para poder consultarlos desde otros métodos:&lt;/p&gt; &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; Stopwatch downloadStopWatch = &lt;span class="kwrd"&gt;new&lt;/span&gt; Stopwatch();&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; Int64 downloadedBytes=0;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cuando vamos a iniciar la lectura, iniciamos el StopWatch y vamos incrementando los bytes leídos:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; (FileStream fs = &lt;span class="kwrd"&gt;new&lt;/span&gt; FileStream(&lt;span class="kwrd"&gt;this&lt;/span&gt;.localDirectory, &lt;/pre&gt;&lt;pre&gt;       FileMode.Create,FileAccess.Write, FileShare.None))&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; (Stream strm = ftp.GetResponse().GetResponseStream())&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Inicio el cronómetro&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  downloadStopWatch.Start();&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;  contentLen = strm.Read(buff, 0, &lt;span class="kwrd"&gt;this&lt;/span&gt;.bufferLength);&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Incremento los bytes leidos.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  downloadedBytes += contentLen;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="kwrd"&gt;while&lt;/span&gt; (contentLen != 0)&lt;/pre&gt;&lt;pre class="alt"&gt;  {&lt;/pre&gt;&lt;pre&gt;    fs.Write(buff, 0, contentLen);&lt;/pre&gt;&lt;pre class="alt"&gt;    contentLen = strm.Read(buff, 0, &lt;span class="kwrd"&gt;this&lt;/span&gt;.bufferLength);&lt;/pre&gt;&lt;pre&gt;    &lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="rem"&gt;// Incremento los bytes leidos.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;    downloadedBytes += contentLen;&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Paro el cronómetro.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  downloadStopWatch.Stop();&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cuando queramos saber la velocidad, por ejemplo en KBytes/s podemos invocar a un metodo/propiedad-get tal que así:&lt;/p&gt;
&lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; Single DownloadSpeed&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  get&lt;/pre&gt;&lt;pre&gt;  {&lt;/pre&gt;&lt;pre class="alt"&gt;    Double bytesPerSecond;&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;try&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;    {&lt;/pre&gt;&lt;pre&gt;      bytesPerSecond = (&lt;span class="kwrd"&gt;this&lt;/span&gt;.downloadedBytes / &lt;/pre&gt;&lt;pre class="alt"&gt;                        downloadStopWatch.ElapsedMilliseconds) * 1000;&lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;catch&lt;/span&gt; (DivideByZeroException)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// El StopWatch marcaba 0 milisegundos.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      bytesPerSecond = &lt;span class="kwrd"&gt;this&lt;/span&gt;.downloadedBytes;&lt;/pre&gt;&lt;pre class="alt"&gt;    }&lt;/pre&gt;&lt;pre&gt;    &lt;span class="kwrd"&gt;return&lt;/span&gt; (Single)(bytesPerSecond / 1024);&lt;/pre&gt;&lt;pre class="alt"&gt;  }&lt;/pre&gt;&lt;pre&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Ojo, se puede dar el caso de que consultemos la velocidad y la descarga no se haya iniciado aún ... ó que ni siquiera haya pasado 1ms ... por lo que debemos controlar la división por cero y devolver en dicho caso los bytes transferidos hasta el momento, que serán 0 si la descarga no ha comenzado y X si ha comenzado ya :D ... &lt;strong&gt;Yo siempre me pongo en el peor de los supuestos por si acaso !!!!&lt;/strong&gt; xD&lt;/p&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Medir-la-velocidad-de-un-Stream.aspx"&gt;Medir la velocidad de un Stream | vtortola.NET&lt;/a&gt;&lt;/h5&gt;
Crossposting desde &lt;a href="http://elbruno.com/blogs/vtortola/"&gt;ElBruno.com&lt;/a&gt;&lt;img src="http://blog.avanadeadvisor.com/aggbug.aspx?PostID=5457" width="1" height="1"&gt;</content><author><name>vtortola</name><uri>http://blog.avanadeadvisor.com/members/vtortola.aspx</uri></author></entry><entry><title>Evaluando el modelo</title><link rel="alternate" type="text/html" href="http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/18/5213.aspx" /><id>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/18/5213.aspx</id><published>2007-09-19T03:04:54Z</published><updated>2007-09-19T03:04:54Z</updated><content type="html">&lt;p&gt;Antes de empezar&amp;nbsp;a desarrollar una aplicación, es conveniente revisar el diseño lógico&amp;nbsp;para asegurar que es esta completo y correcto. Esto viene a ser evaluar&amp;nbsp;el diseño en los siguientes terminos:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Evaluación de ejecución:  &lt;ul&gt; &lt;li&gt;Rendimiento.  &lt;li&gt;Escalabilidad.  &lt;li&gt;Disponibilidad.  &lt;li&gt;Recuperación.  &lt;li&gt;Seguridad.&lt;/li&gt;&lt;/ul&gt; &lt;li&gt;Evaluación de la&amp;nbsp;arquitectura:  &lt;ul&gt; &lt;li&gt;Mantenibilidad.  &lt;li&gt;Extensibilidad.&lt;/li&gt;&lt;/ul&gt; &lt;li&gt;Evaluación de los requerimientos:  &lt;ul&gt; &lt;li&gt;Casos de uso.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;La &lt;strong&gt;evaluación del rendimiento&lt;/strong&gt; determina la velocidad de ejecución de la aplicación. Desde el diseño lógico, tiene dos puntos fundamentales:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;em&gt;Las capas&lt;/em&gt;: Asegurar que no se ha dividido en demasiadas &lt;a href="http://www.vtortola.net/post/Modelado-en-capas.aspx"&gt;capas&lt;/a&gt;. Normalmente 3 son suficientes, aunque puede necesitarse más si existen motivos razonables.  &lt;li&gt;&lt;em&gt;Los niveles de abstracción&lt;/em&gt;: Hay que evitar niveles de abstracción innecesarios en las clases. Un abuso podría producir una perdida de rendimiento al forzar al flujo de ejecución a pasar por demasiados objetos.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;A nivel de diseño lógico, evaluar el rendimiento queda limitado a la búsqueda y supresión de redundancias.&lt;/p&gt; &lt;p&gt;La &lt;strong&gt;evaluación de la escalabilidad&lt;/strong&gt; determina la capacidad de la aplicación para adaptarse al aumento de carga y/ó número de usuarios. Es importante asegurar que dispone de una ó varias capas intermedias separadas, de forma que dichas capas puedan ser puestas en otras máquinas, como por ejemplo en un servidor de gran capacidad mediante Remoting.&lt;/p&gt; &lt;p&gt;La &lt;strong&gt;evaluación de la disponibilidad&lt;/strong&gt; y recuperación determina la capacidad de la aplicación para recuperarse y tomar contramedidas en situaciones catastróficas. Es importante asegurar que las clases pueden:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Usar transacciones fiables, ya sean de base de datos, MSMQ, System.EnterpriseServices, DTC, ...etc... De forma que en caso de falla no quede una operación colgada a mitad de realizarse.  &lt;li&gt;Reconstruir archivos de datos y configuración corruptos por un fallo de continuidad en el hardware, como una perdida de alimentación.  &lt;li&gt;Tomar fuentes de datos ó servicios redundantes en caso de perdidas de conectividad ó fallos del hardware remoto.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;La &lt;strong&gt;evaluación de la seguridad&lt;/strong&gt; determina la capacidad de la aplicación para proteger "sus secretos" (contraseñas, cadenas de conexión, datos sensibles, ..etc..). Los pilares fundamentales son:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;em&gt;Autenticación&lt;/em&gt;: Solicitar que el usuario se autentique para poder usar la aplicación, por ejemplo a través de 'Active Directory' ó un sistema propio de&amp;nbsp;contrastar usuarios/contraseñas. Es importante que si se usa un sistema propio, no se almacenen las contraseñas si no sus Hash.  &lt;li