<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blog.avanadeadvisor.com/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>vtortola</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/default.aspx</link><description>vtortola.NET</description><dc:language>es</dc:language><generator>CommunityServer 2.0 (Build: 60217.2664)</generator><item><title>Recorrer una estructura en arbol con multiples hilos: ThreadedTreeBase</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/13/5697.aspx</link><pubDate>Sun, 14 Oct 2007 00:01:00 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5697</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5697.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5697</wfw:commentRss><description>&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;</description></item><item><title>Recorrer una estructura en arbol con m&amp;amp;#250;ltiples hilos</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/13/5693.aspx</link><pubDate>Sat, 13 Oct 2007 20:45:16 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5693</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5693.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5693</wfw:commentRss><description>&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;</description></item><item><title>Cliente FTP as&amp;amp;#237;ncrono</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/07/5566.aspx</link><pubDate>Sun, 07 Oct 2007 23:38:00 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5566</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5566.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5566</wfw:commentRss><description>&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;</description></item><item><title>Extendiendo un ProgressBar. FlatProgressBar.</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/06/5540.aspx</link><pubDate>Sat, 06 Oct 2007 20:03:38 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5540</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5540.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5540</wfw:commentRss><description>&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;</description></item><item><title>Optimizaci&amp;amp;#243;n de la evaluaci&amp;amp;#243;n de condiciones</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/05/5516.aspx</link><pubDate>Sat, 06 Oct 2007 02:54:42 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5516</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5516.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5516</wfw:commentRss><description>&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;</description></item><item><title>CLR String Interning</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/04/5506.aspx</link><pubDate>Fri, 05 Oct 2007 02:31:55 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5506</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5506.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5506</wfw:commentRss><description>&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;</description></item><item><title>Se publicar&amp;amp;#225;n las fuentes de la BCL de .NET</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/10/04/5501.aspx</link><pubDate>Thu, 04 Oct 2007 12:53:00 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5501</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5501.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5501</wfw:commentRss><description>&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;</description><category domain="http://blog.avanadeadvisor.com/blogs/vtortola/archive/category/1206.aspx">.NET</category></item><item><title>Medir la velocidad de un Stream</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/28/5457.aspx</link><pubDate>Fri, 28 Sep 2007 22:42:46 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5457</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5457.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5457</wfw:commentRss><description>&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;</description></item><item><title>Evaluando el modelo</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/18/5213.aspx</link><pubDate>Wed, 19 Sep 2007 03:04:54 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5213</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5213.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5213</wfw:commentRss><description>&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&gt;&lt;em&gt;Cifrado&lt;/em&gt;: Asegurar que los datos sensibles que necesariamente se han de guardar en disco, solo puedan ser usados por el usuario que los creo. Por ejemplo, usando la &lt;a href="http://msdn2.microsoft.com/es-es/library/ms229741(VS.80).aspx"&gt;DPAPI&lt;/a&gt;. Se debe reducir al máximo el guardado de archivos temporales con datos sensibles. Crear un almacenamiento aislado es una buena opción para ello.  &lt;li&gt;&lt;em&gt;SQL-Injections&lt;/em&gt;: Asegurar que desde ningún formulario de la aplicación ó punto visible se puede realizar una &lt;a href="http://es.wikipedia.org/wiki/Inyecci%25C3%25B3n_SQL"&gt;injección de código SQL&lt;/a&gt;. La mejor forma de evitar esto es usando &lt;a href="http://www.vtortola.net/post/DbParameter.aspx"&gt;parámetros&lt;/a&gt; para construir las sentencias SQL.  &lt;li&gt;&lt;em&gt;Auditoria&lt;/em&gt;: Se debe auditar los procesos de autenticación y autorización, de forma que podamos comprobar si hay usuarios intentando acceder a datos protegidos ó intentando autenticarse por fuerza bruta.  &lt;li&gt;Firmar los ensamblados con nombres seguros para que solo el autor original de la aplicación pueda substituirlos.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;La &lt;strong&gt;evaluación de la mantenibilidad&lt;/strong&gt; determina la facilidad con la que la aplicación podrá se mantenida una vez puesta en producción. Siguiendo un diseño en &lt;a href="http://www.vtortola.net/post/Modelado-en-capas.aspx"&gt;capas&lt;/a&gt; y modular se asegura que los cambios podrán realizarse substituyendo piezas de la aplicación y no&amp;nbsp;la aplicación entera.&lt;/p&gt; &lt;p&gt;La &lt;strong&gt;evaluación de la extensibilidad&lt;/strong&gt; determina si el código de la aplicación es rehusable ó si se puede implementar con soluciones ya hechas, bien en el .NET Framework ó de nuestra propia empresa. Usando código ya existente aseguramos que utilizamos cosas ya probadas y ahorramos costes de desarrollo.&lt;/p&gt; &lt;p&gt;La &lt;strong&gt;evaluación de la integridad de los datos&lt;/strong&gt; determina dos cosas:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;La capacidad de la aplicación para mantener los datos acorde al esquema de la fuente, de forma que se respeten resticiones, tipos y contratos, asegurando así que la fuente no rechazará los datos cuando le sean enviados.  &lt;li&gt;La capacidad de la aplicación para lidiar con la concurrencia de los datos. Existen dos tipos de concurrencia:  &lt;ul&gt; &lt;li&gt;&lt;em&gt;Optimista&lt;/em&gt;: Los datos no se bloquean mientras estan siendo leidos por otro usuario. Mejor rendimiento pero se tiene el riesgo de que los datos cambien mientras están siendo leidos, y podrían tomarse decisiones erróneas.  &lt;li&gt;&lt;em&gt;Pesimista&lt;/em&gt;: Los datos se bloquean mientras están siendo leidos por otro usuario. Peor rendimiento pero asegura que cuando un usuario lee un dato, ese dato no cambiará hasta que termine de trabajar con él.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;La &lt;strong&gt;evaluación de los casos de uso&lt;/strong&gt; determina si la aplicación sigue cumpliendo con los &lt;a href="http://www.vtortola.net/post/Requerimientos-de-un-proyecto.aspx"&gt;requerimientos&lt;/a&gt;. Se intenta buscar inconsistencias ó ambiguedades en el diseño lógico.&lt;/p&gt; &lt;p&gt;Resumen de la lección 3.1 del &lt;a href="http://www.microsoft.com/MSPress/books/10093.aspx"&gt;libro oficial de Microsoft&lt;/a&gt; para obtener el &lt;a href="http://www.microsoft.com/learning/mcp/mcpd/windev/default.mspx"&gt;MCPD:WinApps&lt;/a&gt;.&lt;/p&gt; &lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Evaluando-el-modelo.aspx"&gt;Evaluando el diseño | 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=5213" width="1" height="1"&gt;</description></item><item><title>Creando modelos para desarrolladores</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/16/5162.aspx</link><pubDate>Mon, 17 Sep 2007 01:21:22 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5162</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5162.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5162</wfw:commentRss><description>&lt;p&gt;Siguiendo con el tema del &lt;a href="http://www.vtortola.net/post/Modelado-en-capas.aspx"&gt;modelado del software en capas&lt;/a&gt;, es el turno de crear modelos físicos más precisos para los programadores que han de escribir el código. Los modelos físicos indican como los programadores deberían desarrollar la aplicación.&lt;/p&gt; &lt;p&gt;Un &lt;a href="http://www.programacion.net/tutorial/uml/6/"&gt;diagrama de componentes&lt;/a&gt; indica los componentes ó paquetes de los que se compondrá la aplicación y las dependencias de estos. Un componente es un conjunto de clases relacionadas formando una unidad distribuible, como una DLL, un control ó un Webservice. Los componentes se representan por rectangulos con dos pequeños rectangulos a su izquierda,&amp;nbsp;en el nombre se indica la capa a la que pertenecen separados&amp;nbsp;con dos dobles puntos ('::'). Otros rectangulos mayores que agrupan varios componentes simbolizan un contenedor de distribución llamado nodo, que suele indicar una separación física, como estar en distinto hardware.&lt;/p&gt; &lt;p&gt;Los &lt;a href="http://www.programacion.net/tutorial/uml/4/"&gt;diagramas de clases&lt;/a&gt; definen las estructura de las clases y las relaciones entre ellas desde el punto de vista de la programación orientada a objetos, sin especificar como interactuan entre ellas, solo un modelo estático. Este modelo que es independiente de la tecnología y lenguaje usado,&amp;nbsp;representa las clases, sus propiedades, métodos, interfaces,&amp;nbsp;herencia, asociaciones, tipos de datos y demás. Las clases deben derivar de la arquitectura propuesta para la solución.&lt;/p&gt; &lt;p&gt;Los &lt;a href="http://www.programacion.net/tutorial/uml/8/"&gt;diagramas de secuencia&lt;/a&gt; muestran como los objetos intearactuan entre ellos durante la ejecución, muestra el tiempo de vida de dicho objetos y los mensajes intercambiados entre ellos. Los objetos se representan como rectangulos en lo alto del diagrama con lineas verticales donde se representa su tiempo de vida y horizontales representando el envio de mensajes a otros objetos.&lt;/p&gt; &lt;p&gt;Un diagrama de colaboración muestra el mismo tipo de interacción que el anterior solo que permite distribuirlo todo de una forma más libre, indicando el orden de los mensajes mediante una numeración.&lt;/p&gt; &lt;p&gt;Un &lt;a href="http://es.wikipedia.org/wiki/Diagrama_de_flujo"&gt;diagrama de actividad ó flujo&lt;/a&gt;&amp;nbsp;representa el flujo de ejecución y permite mostrar actividades que suceden de forma paralela (ó multithreading). Se usan para representar algorritmos más complejos.&lt;/p&gt; &lt;p&gt;El &lt;a href="http://es.wikipedia.org/wiki/Pseudoc%C3%B3digo"&gt;pseudo-código&lt;/a&gt; no es un diagrama, ni es un lenguaje específico, es una forma de representar el código que ha de realizar el programador de una forma representativa. Es una forma rápida y fácil para los arquitectos y analistas de expresar conceptos a los programadores. Es como cada uno quiera hacerlo, ya sea por facilidad para si mismo ó por facilidad para quien tiene que interprestarlo.&lt;/p&gt; &lt;p&gt;Resumen de la lección 2.3 del &lt;a href="http://www.microsoft.com/MSPress/books/10093.aspx"&gt;libro oficial de Microsoft&lt;/a&gt; para obtener el &lt;a href="http://www.microsoft.com/learning/mcp/mcpd/windev/default.mspx"&gt;MCPD:WinApps&lt;/a&gt;.&lt;/p&gt; &lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Creando-modelos-para-desarrolladores.aspx"&gt;Creando modelos para desarrolladores | 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=5162" width="1" height="1"&gt;</description></item><item><title>DbTransaction, transaciones en un Db Factory</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/16/5155.aspx</link><pubDate>Sun, 16 Sep 2007 21:08:02 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5155</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5155.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5155</wfw:commentRss><description>&lt;p&gt;Siguiendo con el tema del &lt;a href="http://www.vtortola.net/post/DBFactory-con-ADONET-20.aspx"&gt;DbFactory&lt;/a&gt; con las clases de &lt;em&gt;System.Data.Common,&lt;/em&gt; que permiten trabajar con cualquier base de datos de la que tengamos un proveedor en .NET, explicaré la clase &lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.common.dbtransaction%28VS.80%29.aspx"&gt;DbTransaction&lt;/a&gt;, el cual permite realizar &lt;a href="http://es.wikipedia.org/wiki/Transacci%C3%B3n_%28base_de_datos%29"&gt;transacciones&lt;/a&gt; en la base de datos (explicación más completa en la &lt;a href="http://en.wikipedia.org/wiki/Database_transaction"&gt;versión en inglés&lt;/a&gt;).  &lt;p&gt;Una transacción es una operación formada por un grupo de operaciones relacionadas, cada una podría requerir varias operaciones de lectura y/ó escritura en la base de datos, y solo tiene validez si se ejecutan todas. En caso de producirse un error en el transcurso de la transacción, ha de ser posible “deshacer” todas las operaciones anteriores (&lt;em&gt;rollback&lt;/em&gt;), en caso contrario se da por finalizada la transacción aplicando los cambios (&lt;em&gt;commit&lt;/em&gt;).  &lt;p&gt;Por ejemplo, en un traspaso de dinero entre dos cuentas del mismo banco, la operación incluye primero la substracción del dinero de la cuenta A y su ingreso en la cuenta B, imaginemos que la substracción se realiza correctamente pero hay un error a la hora de ingresar la cantidad en B, en este caso el dinero no quedaría ni en una cuenta ni en otra y podría hasta “perderse”; si esta operación se ejecuta como una transacción, al fallar el ingreso en B, automáticamente se revertiría la substracción en A, con lo que ambas cuentas volverían a quedar en el mismo estado que antes de ejecutarse dicha transacción.  &lt;p&gt;Siguiendo con el &lt;a href="http://www.vtortola.net/post/DBFactory-con-ADONET-20.aspx"&gt;ejemplo anterior en DbFactory&lt;/a&gt; y volviendo a usar la misma base de datos de ejemplo &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=E719ECF7-9F46-4312-AF89-6AD8702E4E6E&amp;amp;displaylang=en"&gt;Northwind&lt;/a&gt; que viene incluida en &lt;a href="http://www.microsoft.com/spanish/msdn/vstudio/express/SQL/default.mspx"&gt;SQL Server 2005 Express&lt;/a&gt;, en este ejemplo se realizará la inserción de 5 nuevos empleados como una transacción, si todo funciona correctamente se realizará un &lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.common.dbtransaction.commit%28VS.80%29.aspx"&gt;DbTransaction.Commit()&lt;/a&gt; que finalizaría la transacción guardando los datos, mientras que si en el transcurso de la operación se produjese alguna excepción, se ejecutaría &lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.common.dbtransaction.rollback%28VS.80%29.aspx"&gt;DbTransaction.Rollback()&lt;/a&gt; devolviendo a la tabla a su estado original. A partir de este código probad a introducir errores en las sentencias SQL para ver como al saltar una &lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.common.dbexception%28VS.80%29.aspx"&gt;DbException&lt;/a&gt; se realiza un &lt;em&gt;rollback&lt;/em&gt;.  &lt;p&gt;&amp;nbsp;  &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; provider = ConfigurationManager.ConnectionStrings[&lt;span class="str"&gt;"SQLServer"&lt;/span&gt;].ProviderName;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; connectionString = ConfigurationManager.ConnectionStrings[&lt;span class="str"&gt;"SQLServer"&lt;/span&gt;].ConnectionString;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="rem"&gt;// Sentencia SQL&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; insertEmployee = &lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="str"&gt;"INSERT INTO EMPLOYEES (EmployeeID, LastName, FirstName) VALUES ({0}, ‘{1}’, ‘{2}’)"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;&lt;span class="rem"&gt;// Obtengo el proovedor.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;DbProviderFactory dbpf = DbProviderFactories.GetFactory(provider);&lt;/pre&gt;&lt;pre&gt;&lt;span class="rem"&gt;// Creo la conexión y el comando.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; (DbConnection dbcon = dbpf.CreateConnection())&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; (DbCommand dbcmd = dbcon.CreateCommand())&lt;/pre&gt;&lt;pre class="alt"&gt;{&lt;/pre&gt;&lt;pre&gt;&lt;span class="rem"&gt;// Configuro la conexión&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;dbcon.ConnectionString = connectionString;&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="rem"&gt;// Abro la conexión&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcon.Open();&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Inicio la transación&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.Transaction = dbcon.BeginTransaction();&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Si os falla al insertar filas probad esto:&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.CommandText = &lt;span class="str"&gt;"SET IDENTITY_INSERT Employees ON"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;  dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;////&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Inserto a los nuevos empleados&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.CommandText = &lt;span class="kwrd"&gt;string&lt;/span&gt;.Format(insertEmployee, 10, &lt;span class="str"&gt;"Simpson"&lt;/span&gt;, &lt;span class="str"&gt;"Homer"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.CommandText = &lt;span class="kwrd"&gt;string&lt;/span&gt;.Format(insertEmployee, 11, &lt;span class="str"&gt;"Szyslak"&lt;/span&gt;, &lt;span class="str"&gt;"Moe"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.CommandText = &lt;span class="kwrd"&gt;string&lt;/span&gt;.Format(insertEmployee, 12, &lt;span class="str"&gt;"Carlson"&lt;/span&gt;, &lt;span class="str"&gt;"Karl"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.CommandText = &lt;span class="kwrd"&gt;string&lt;/span&gt;.Format(insertEmployee, 13, &lt;span class="str"&gt;"Leonard"&lt;/span&gt;, &lt;span class="str"&gt;"Lenny"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.CommandText = &lt;span class="kwrd"&gt;string&lt;/span&gt;.Format(insertEmployee, 14, &lt;span class="str"&gt;"Gamble"&lt;/span&gt;, &lt;span class="str"&gt;"Barney"&lt;/span&gt;);&lt;/pre&gt;&lt;pre&gt;  dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// No ha saltado ninguna excepción,&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// todo ha ido bien, hago commit y salvo la operación.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.Transaction.Commit();&lt;/pre&gt;&lt;pre&gt;  Console.WriteLine(&lt;span class="str"&gt;"Transacción OK!"&lt;/span&gt;);&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;catch&lt;/span&gt; (DbException dbex)&lt;/pre&gt;&lt;pre class="alt"&gt;{&lt;/pre&gt;&lt;pre&gt;  &lt;span class="rem"&gt;// Algo salío mal, deshago la transacción.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;  dbcmd.Transaction.Rollback();&lt;/pre&gt;&lt;pre&gt;  Console.WriteLine(dbex.Message);&lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nota: Aunque en el ejemplo se componen las SQL "a pelo" por brevedad, lo recomendable es usar &lt;a href="http://www.vtortola.net/post/DbParameter.aspx"&gt;parámetros con DbParameter&lt;/a&gt;.&lt;/p&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/DbTransaction%2c-transaciones-en-un-Db-Factory.aspx"&gt;DbTransaction, transaciones en un Db Factory | 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=5155" width="1" height="1"&gt;</description></item><item><title>DbParameter.</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/16/5154.aspx</link><pubDate>Sun, 16 Sep 2007 20:56:14 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5154</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5154.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5154</wfw:commentRss><description>&lt;p&gt;Para evitar problemas de seguridad en ADO.NET 2.0 es recomendable componer las sentencias SQL a base de parámetros en lugar de construir las cadenas manualmente en base a las propiedades .Text de los controles u otros orígenes. De esta forma evitamos fallos de seguridad potenciales (ie: &lt;a href="http://es.wikipedia.org/wiki/Inyecci%25C3%25B3n_SQL"&gt;SQL Injection&lt;/a&gt;) y otros problemas menores. Siguiendo con nuestra &lt;a href="http://www.vtortola.net/post/DBFactory-con-ADONET-20.aspx"&gt;DB Factory&lt;/a&gt; donde&amp;nbsp;mostré como utilizar la &lt;a href="http://www.microsoft.com/spanish/msdn/comunidad/mtj.net/voices/MTJ_3624.asp"&gt;Fábrica de objetos&lt;/a&gt; de ADO.NET 2.0 para construir objetos que pueden manejar cualquier base de datos, ahora le toca el turno a la clase DbParameter para añadir parámetros a las sentencias SQL de nuestra DB Factory.  &lt;p&gt;[more]  &lt;p&gt;&lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.common.dbparameter%28VS.80%29.aspx"&gt;DbParameter&lt;/a&gt;, también es una clase abstracta de &lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.common%28VS.80%29.aspx"&gt;System.Data.Common&lt;/a&gt; que especializan todas las clases con las que estamos más acostumbrados a trabajar, como &lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.sqlclient.sqlparameter%28VS.80%29.aspx"&gt;SqlParameter&lt;/a&gt;, &lt;a href="http://msdn2.microsoft.com/es-es/library/system.data.oracleclient.oracleparameter%28VS.80%29.aspx"&gt;OracleParameter&lt;/a&gt;, … etc.. Por lo que seguimos con las mismas ventajas y pegas que las que tenemos en general con Db Factory, la ventaja de la gran versatilidad y la desventaja de no poder acceder a las propiedades especializadas de la clase derivada, ambas causadas por el &lt;a href="http://es.wikipedia.org/wiki/Polimorfismo_%28programaci%25C3%25B3n_orientada_a_objetos%29"&gt;polimorfismo&lt;/a&gt;  &lt;p&gt;El ejemplo, que usa los mismos datos de conexión y BD que&amp;nbsp;el artículo sobre DbFactory,&amp;nbsp;representa como insertar un trabajador en la tabla 'Employees':  &lt;p&gt;&amp;nbsp;  &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; provider = ConfigurationManager.ConnectionStrings[&lt;span class="str"&gt;"SQLServer"&lt;/span&gt;].ProviderName;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; connectionString = ConfigurationManager.ConnectionStrings[&lt;span class="str"&gt;"SQLServer"&lt;/span&gt;].ConnectionString;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;&lt;span class="rem"&gt;// Sentencia SQL&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;string&lt;/span&gt; insertEmployee = &lt;/pre&gt;&lt;pre&gt;  &lt;span class="str"&gt;"INSERT INTO EMPLOYEES (EmployeeID, LastName, FirstName) VALUES (@ID, @LNAME, @NAME)"&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 el proovedor.&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;DbProviderFactory dbpf = DbProviderFactories.GetFactory(provider);&lt;/pre&gt;&lt;pre&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="rem"&gt;// Creo la conexión y el comando.&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; (DbConnection dbcon = dbpf.CreateConnection())&lt;/pre&gt;&lt;pre class="alt"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; (DbCommand dbcmd = dbcon.CreateCommand())&lt;/pre&gt;&lt;pre&gt;{&lt;/pre&gt;&lt;pre class="alt"&gt;  &lt;span class="rem"&gt;// Configuro la conexión&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;  dbcon.ConnectionString = connectionString;&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;      Console.WriteLine(&lt;span class="str"&gt;"Ejecutando SQL con un obteto de tipo {0}."&lt;/span&gt;, &lt;/pre&gt;&lt;pre&gt;                         dbcon.GetType().Name);&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// Abro la conexión&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      dbcon.Open();&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// Si os falla al insertar filas probad esto:&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      dbcmd.CommandText = &lt;span class="str"&gt;"SET IDENTITY_INSERT Employees ON"&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;      dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;////&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      dbcmd.CommandText = insertEmployee;&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// Parámetros&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      DbParameter dbparam = dbcmd.CreateParameter();&lt;/pre&gt;&lt;pre&gt;      dbparam.DbType = DbType.String;&lt;/pre&gt;&lt;pre class="alt"&gt;      dbparam.ParameterName = &lt;span class="str"&gt;"NAME"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;      dbcmd.Parameters.Add(dbparam);&lt;/pre&gt;&lt;pre class="alt"&gt;      dbparam = dbcmd.CreateParameter();&lt;/pre&gt;&lt;pre&gt;      dbparam.DbType = DbType.String;&lt;/pre&gt;&lt;pre class="alt"&gt;      dbparam.ParameterName = &lt;span class="str"&gt;"LNAME"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;      dbcmd.Parameters.Add(dbparam);&lt;/pre&gt;&lt;pre class="alt"&gt;      dbparam = dbcmd.CreateParameter();&lt;/pre&gt;&lt;pre&gt;      dbparam.DbType = DbType.Int32;&lt;/pre&gt;&lt;pre class="alt"&gt;      dbparam.ParameterName = &lt;span class="str"&gt;"ID"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;      dbcmd.Parameters.Add(dbparam);&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// Inserto a los nuevos empleados&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      dbcmd.Parameters[&lt;span class="str"&gt;"NAME"&lt;/span&gt;].Value = &lt;span class="str"&gt;"Valeriano"&lt;/span&gt;;&lt;/pre&gt;&lt;pre&gt;      dbcmd.Parameters[&lt;span class="str"&gt;"LNAME"&lt;/span&gt;].Value = &lt;span class="str"&gt;"Tortola"&lt;/span&gt;;&lt;/pre&gt;&lt;pre class="alt"&gt;      dbcmd.Parameters[&lt;span class="str"&gt;"ID"&lt;/span&gt;].Value = 15;&lt;/pre&gt;&lt;pre&gt;      dbcmd.ExecuteNonQuery();&lt;/pre&gt;&lt;pre class="alt"&gt;&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// No ha saltado ninguna excepción,&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      &lt;span class="rem"&gt;// todo ha ido bien. También podríamos&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;      &lt;span class="rem"&gt;// haberlo hecho como una transación :)&lt;/span&gt;&lt;/pre&gt;&lt;pre class="alt"&gt;      Console.WriteLine(&lt;span class="str"&gt;"Inserción OK!"&lt;/span&gt;); &lt;/pre&gt;&lt;pre&gt;    }&lt;/pre&gt;&lt;pre class="alt"&gt;    &lt;span class="kwrd"&gt;catch&lt;/span&gt; (DbException dbex)&lt;/pre&gt;&lt;pre&gt;    {&lt;/pre&gt;&lt;pre class="alt"&gt;      Console.WriteLine(dbex.Message); &lt;/pre&gt;&lt;pre&gt;    } &lt;/pre&gt;&lt;pre class="alt"&gt;}&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/DbParameter.aspx"&gt;DbParameter.| 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=5154" width="1" height="1"&gt;</description></item><item><title>Localizar carpetas especiales de Windows</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/15/5145.aspx</link><pubDate>Sun, 16 Sep 2007 03:48:14 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5145</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5145.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5145</wfw:commentRss><description>&lt;p&gt;Esto lo ví una vez y a raiz de una &lt;a href="http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2144937&amp;amp;SiteID=1"&gt;pregunta en el foro&lt;/a&gt; lo busqué de nuevo. Se trata de como localizar las rutas a las carpetas especiales de Windows desde .NET, aunque existe una variable de entorno&amp;nbsp;&lt;em&gt;%homepath%&lt;/em&gt; apuntando a nuestra carpeta personal en el sistema, no hay ninguna para llegar a carpetas como 'Escritorio', 'Mis Imágenes', 'Mi Música', ... etc ... y dependiendo del idioma en el que este el sistema operativo, pueden llamarse 'Desktop', 'My Pictures', 'My Music', ...etc... &lt;/p&gt; &lt;p&gt;Existe una enumeración &lt;a href="http://msdn2.microsoft.com/es-es/library/system.environment.specialfolder(vs.80).aspx#Mtps_DropDownFilterText"&gt;System.Environment.SpecialFolder&lt;/a&gt; que apunta a dichas carpetas, para obtener la ruta se usa el método &lt;a href="http://msdn2.microsoft.com/es-es/library/system.environment.getfolderpath(VS.80).aspx"&gt;System.Environment.GetFolderPath&lt;/a&gt;:&lt;/p&gt; &lt;div class="csharpcode"&gt;&lt;pre class="alt"&gt;&lt;span class="rem"&gt;//Obtengo la ruta de "Mis Imágenes"&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;String ruta = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Las rutas especiales que podemos encontrar son (sacado del MSDN):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;ApplicationData: &lt;/b&gt;Directorio que sirve de repositorio común de datos específicos de la aplicación para el usuario móvil actual.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;CommonApplicationData: &lt;/b&gt;Directorio que sirve de repositorio común de datos específicos de la aplicación que todos los usuarios utilizan.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;CommonProgramFiles: &lt;/b&gt;Directorio de componentes que se comparten entre distintas aplicaciones.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Cookies: &lt;/b&gt;Directorio que sirve de repositorio común para las cookies de Internet.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Desktop: &lt;/b&gt;El escritorio lógico en vez de la ubicación física del sistema de archivos.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;DesktopDirectory: &lt;/b&gt;Directorio que se utiliza para almacenar objetos de archivo físicamente en el escritorio.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Favorites: &lt;/b&gt;Directorio que sirve de repositorio común para los elementos favoritos del usuario.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;History: &lt;/b&gt;Directorio que sirve de repositorio común para los elementos del historial de Internet.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;InternetCache: &lt;/b&gt;Directorio que sirve de repositorio común para los archivos temporales de Internet.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;LocalApplicationData: &lt;/b&gt;Directorio que sirve de repositorio común para datos específicos de la aplicación que el usuario no móvil actual utiliza.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;MyComputer: &lt;/b&gt;La carpeta "Mi PC".&amp;nbsp;La constante &lt;a href="http://msdn2.microsoft.com/es-es/library/system.environment.specialfolder.mycomputer%28VS.80%29.aspx"&gt;MyComputer&lt;/a&gt; siempre produce una cadena vacía ("") porque no hay ninguna ruta de acceso definida para la carpeta Mi PC. 
&lt;li&gt;&lt;b&gt;MyDocuments: &lt;/b&gt;La carpeta "Mi PC".&amp;nbsp; 
&lt;li&gt;&lt;b&gt;MyMusic: &lt;/b&gt;La carpeta "Mi música".&amp;nbsp; 
&lt;li&gt;&lt;b&gt;MyPictures: &lt;/b&gt;La carpeta "Mis imágenes".&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Personal: &lt;/b&gt;Directorio que sirve de repositorio común para documentos.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;ProgramFiles: &lt;/b&gt;Directorio de archivos de programa.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Programs: &lt;/b&gt;Directorio que contiene los grupos de programas del usuario.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Recent: &lt;/b&gt;Directorio que contiene los documentos utilizados más recientemente por el usuario.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;SendTo: &lt;/b&gt;Directorio que contiene los elementos de menú Enviar a.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;StartMenu: &lt;/b&gt;Directorio que contiene los elementos de menú Inicio.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Startup: &lt;/b&gt;Directorio que se corresponde con el grupo de programas Inicio del usuario.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;System: &lt;/b&gt;Directorio del sistema.&amp;nbsp; 
&lt;li&gt;&lt;b&gt;Templates: &lt;/b&gt;Directorio que sirve de repositorio común para plantillas de documentos.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;
&lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Localizar-carpetas-especiales-de-Windows.aspx"&gt;Localizar carpetas especiales de Windows | 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=5145" width="1" height="1"&gt;</description></item><item><title>Los pools de programadores</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/15/5123.aspx</link><pubDate>Sat, 15 Sep 2007 05:37:48 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5123</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5123.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5123</wfw:commentRss><description>&lt;p&gt;Me gusta todo lo relacionado con IT, puedo construir lo que que quiera, mi herramienta es un ordenador y mis materias primas la imaginación y el conocimiento, el límite esta en cuanto puedo aprender, eso me hace sentirme...&amp;nbsp;bien.&amp;nbsp;Empece en telecomunicaciones, pasé a sistemas y fortuitamente empecé a programar con VB6 y después con Delphi, cuando descubrí C# y .NET decidí dedicarme únicamente a ello pues me encantaba, me resultaba creativo y divertido. Llevo escasos dos años en el mundo del desarrollo en continuo avance para mejorar, pero últimamente estoy algo desmoralizado pues después de haber estado este tiempo desarrollando en plan "ágil",&amp;nbsp;ahora he chocado frontalmente con los conceptos de &lt;em&gt;&lt;a href="http://en.wikipedia.org/wiki/Software_factory"&gt;software factory&lt;/a&gt;&lt;/em&gt; y &lt;em&gt;pool de programadores&lt;/em&gt;, formas de convertir el desarrollo en una cadena de montaje sin sentido supuestamente&amp;nbsp;en pos de la máxima productividad y a mi parecer&amp;nbsp;en decrimento de la calidad, la innovación y la creatividad, formas que ya no me parecen divertidas ni motivadoras.  &lt;p&gt;Verdaderamente pienso que el único propósito de estos 'pools' es convertir a los programadores en partes facilmente intercambiables de un equipo. Un equipo de desarrollo ágil es reducido pero necesita buenos profesionales, que&amp;nbsp;cobrán más ó se ha invertido tiempo/dinero en formarlos, y que el día que deciden irse causan un verdadero problema pues no son piezas facilmente reemplazables. Sin embargo en un pool tienes unos pocos analistas y muchos programadores nóveles totalmente reemplazables. En el mejor de los casos con miras a ser analistas también en el futuro lejano, pues no se llega a&amp;nbsp;analista codificando los&amp;nbsp;diseños de otros, si no trabajando con los analistas y haciendo algo de vez en cuando, aunque sea pequeño.&amp;nbsp;En el peor de los casos subcontratados, en outsourcing ó cualquier otro termino&amp;nbsp;relacionado con el &lt;a href="http://es.wikipedia.org/wiki/Bodyshopping"&gt;body shopping&lt;/a&gt;&amp;nbsp;con la esperanza de que la empresa anfitriona los ponga en plantilla después de 6 ó 12 meses, ó quizás nunca. &lt;p&gt;Frecuentemente leo el &lt;a href="http://geeks.ms/blogs/rcorral/default.aspx"&gt;blog de Rodrigo Corral&lt;/a&gt;, que escribe mucho sobre metodologías ágiles como &lt;a href="http://geeks.ms/blogs/rcorral/archive/tags/Scrum/default.aspx"&gt;Scrum&lt;/a&gt;&amp;nbsp;y acabo de leer un post sobre &lt;a href="http://geeks.ms/blogs/rcorral/archive/2007/09/12/el-pool-de-programadores.aspx"&gt;los pools de programadores&lt;/a&gt; digno de elogio, no hay una sola frase en ese post con la que no este de acuerdo, me ha hecho recordar "a que vine a .NET" y en cierta forma ... recuperar un poco el ánimo al ver que no me equivoqué, si no que simplemente estoy donde no debo estar.  &lt;p&gt;No puedo añadir nada más a su genial artículo, recomiendo su lectura.  &lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Los-pools-de-programadores.aspx"&gt;Los pools de programadores | 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=5123" width="1" height="1"&gt;</description></item><item><title>Modelado en capas.</title><link>http://blog.avanadeadvisor.com/blogs/vtortola/archive/2007/09/13/5100.aspx</link><pubDate>Fri, 14 Sep 2007 01:45:36 GMT</pubDate><guid isPermaLink="false">5e51d585-b788-4f7c-85ba-1877739ce145:5100</guid><dc:creator>vtortola</dc:creator><slash:comments>0</slash:comments><comments>http://blog.avanadeadvisor.com/blogs/vtortola/comments/5100.aspx</comments><wfw:commentRss>http://blog.avanadeadvisor.com/blogs/vtortola/commentrss.aspx?PostID=5100</wfw:commentRss><description>&lt;p&gt;El modelado en capas de un sistema representa como se planea dividir el código en partes lógicas ó grupos. La abstracción del código en capas permite mejorar, reparar y reemplazar partes de la aplicación de forma sencilla y sin que afecte a las demás. Además provee una representación lógica y clara de la aplicación.&lt;/p&gt; &lt;p&gt;El modelo en capas más comun es el de tres capas, presentación, lógica de negocio y base de datos. El model n-capas extiende este modelo de forma que cualquier capa puede dividirse en subcapas, aportando así mayor claridad.&lt;/p&gt; &lt;p&gt;La capa más alta es en la que se interactua con el usuario, la capa más baja donde los datos son almacenados/recuperados y las capas intermedias son las encargadas de realizar el procesado de datos, el movimiento de estos de la base de datos al usuario y el control del estado de la aplicación.&lt;/p&gt; &lt;p&gt;El número de capas que ha de tener una aplicación viene definido por los requerimientos de calidad de servicio, a más altos y exigentes, más capas, pero esto también aumenta el tiempo de desarrollo.&lt;/p&gt; &lt;p&gt;Las capas que comunmente pueden conformar una aplicación Windows son:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Interfaz de usuario&lt;/strong&gt;: Provee la interfaz con la que interactua el usuario. Es responsable de obtener datos de la capa siguiente, mostrarlos, validar entradas de datos, enviarlos a la siguiente capa. Puede dividirse en:  &lt;ul&gt; &lt;li&gt;&lt;em&gt;Pesentación&lt;/em&gt;: Define únicamente la parte de presentación de la interfaz de usuario. Normalmente esta en los archivos *.Designer.cs.  &lt;li&gt;&lt;em&gt;Código de la interfaz de usuario&lt;/em&gt;: Donde los programadores ubican el código para interactuar con la interfaz de usuario respondiendo a eventos.  &lt;li&gt;&lt;em&gt;Código de interacción con la lógica de negocios (Business Façade)&lt;/em&gt;: Esta capa podría usarse para separar totalmente la intefaz de usuario de la lógica de negocio, de forma que podría ser facilment substituible.&lt;/li&gt;&lt;/ul&gt; &lt;li&gt;&lt;strong&gt;Capa Intermedia&lt;/strong&gt;: Donde se ubica la lógica de negocio, aunque también suele incluir cosas como cacheo de datos, registro de actividad, gestión de errores ... etc.... Enterprise Library es un ejemplo de esto. Puede dividirse en:  &lt;ul&gt; &lt;li&gt;&lt;em&gt;Capa de negocios&lt;/em&gt;: En esta capa se ubican los objetos de dominio y&amp;nbsp;sus reglas de negocio, formados por componentes carentes de estado (stateless) que trabajan con System.EnterpriseServices, objetos que son pequeños programas para procesar información, DTOs a través de Remoting, ..etc..  &lt;li&gt;&lt;em&gt;Capa de aplicación (ó Servicios de aplicación)&lt;/em&gt;: Es la capa de mantenimiento general, se encarga normalmente de los requerimientos de calidad de servicio, como las caches, la gestión de excepciones, rendimiento, ..etc.. Enterprise Library es un ejemplo de este tipo de capa.  &lt;li&gt;&lt;em&gt;Capa de acceso a&amp;nbsp;datos (ó Servicios de datos):&lt;/em&gt; Abstrae la obtención y almacenamiento de datos en la base de datos. Una forma de aislarse más de la Capa de datos real.&lt;/li&gt;&lt;/ul&gt; &lt;li&gt;&lt;strong&gt;Capa de datos&lt;/strong&gt;: Representa como se gestionan los datos en la base de datos a través de la&amp;nbsp;aplicación. Normalmente&amp;nbsp;es una base de datos relacional como SQL Server.  &lt;ul&gt; &lt;li&gt;&lt;em&gt;Capa de acceso a datos&lt;/em&gt;:&amp;nbsp;Igual que la capa de datos de la base intermedia.  &lt;li&gt;&lt;em&gt;Procedimientos almacenados&lt;/em&gt;: Representa los procedimientos almacenados nativos ó administrados con los que se manejan datos en la base de datos.  &lt;li&gt;&lt;em&gt;Servicios de integración&lt;/em&gt;:&amp;nbsp;Representa como&amp;nbsp;la base de datos trabaja con otras fuentes de datos.  &lt;li&gt;&lt;em&gt;Tablas, logs y indices de la base de datos&lt;/em&gt;:&amp;nbsp;Los datos en la base de datos.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Una&amp;nbsp;vez definidas las capas&amp;nbsp;es importante ubicarlas bien dependiendo de la arquitectura del sistema y los requerimientos. Por ejemplo, si nuestra aplicación será un SmartClient que se distribuye por internet y no puede acceder directamente a la base de datos, podemos hacer que el cliente conecte a la capa de negocios de un servidor mediante, por ejemplo, Remoting, de forma que la parte servidor pueda validar todas las entradas/salidas de la BD.&lt;/p&gt; &lt;p&gt;Resumen de la lección 2.2 del &lt;a href="http://www.microsoft.com/MSPress/books/10093.aspx"&gt;libro oficial de Microsoft&lt;/a&gt; para obtener el &lt;a href="http://www.microsoft.com/learning/mcp/mcpd/windev/default.mspx"&gt;MCPD:WinApps&lt;/a&gt;.&lt;/p&gt; &lt;h5&gt;&lt;a href="http://www.vtortola.net/post/Modelado-en-capas.aspx"&gt;Modelado en capas. | 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=5100" width="1" height="1"&gt;</description></item></channel></rss>