Qué problema tiene este código? (R)

“See the pages as they turn
In their wisdom we will learn
Free from the prison, a curtain of iron”
(“Curtain of Iron”, Kansas, 1980)

Este post es la continuación (y respuesta) de otro anterior.

Antes que nada, la moraleja de esta historia: ¡estudia la documentación! O en la jerga de un técnico de soporte cabreado, “RTFM” (Read The Freaking Manual). Las guías generales para la publicación de eventos en .NET se resumen en esta página de MSDN, que es de donde extraje (modificándolo para simplificarlo, por una parte, y añadirle el problema, por la otra) el código que presenté la vez anterior.

Una primera desviación del esquema propuesto consiste en que no encapsulé el disparo del evento en la clase publicadora dentro de un método virtual protegido, para permitir la redefinición futura del comportamiento del disparo. Si bien con esto he introducido una limitación en el diseño de la clase, ello no será causante de error de ejecución alguno, como sí ocurre con mi segunda desviación del patrón propuesto.

El problema está en el cambio que introduje en la implementación del disparo del evento. Lo que en el original era algo así como:

            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised
            EventHandler handler = MyEvent;

            // Raise the event. You could also raise the event
            // before executing the block of code
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }

Yo lo traduje alegremente por:

            // Raise the event. You could also raise the event
            // before executing the block of code
            if (MyEvent != null)
            {
                MyEvent(this, EventArgs.Empty);
            }

En otras palabras, me cargué la variable local que se utiliza en el ejemplo original, e hice uso directamente del evento a) para comprobar si está asignado y b) para, en caso afirmativo, dispararlo. En aplicaciones con un único hilo de ejecución esto no presentará problema ninguno; pero en una aplicación con múltiples hilos, como bien reza el comentario en el código, el control podría transferirse a otro hilo después de ejecutar el paso a) con resultado positivo, e inmediatamente antes de ejecutar el paso b). Si en el contexto del hilo de ejecución que tome el control el suscriptor se des-suscribiera del evento, cuando el control retornara al hilo en el que el evento se dispara, MyEvent sería nulo, lo cual provocaría la correspondiente excepción. Estaríamos en presencia de lo que se conoce como una situación de competencia (race condition), un tipo de errores de los programas concurrentes que se manifiestan solo muy ocasionalmente (debido a la escasa probabilidad de que los sucesos se desencadenen en el orden requerido) y que resultan bastante difíciles de reproducir y depurar. Dada la importancia cada vez mayor que tendrá la concurrencia en el futuro, es necesario empezar a tomar conciencia de temas como éste.

Para “simular” la generación de una situación de competencia como la antes descrita, podríamos incorporar temporalmente a la clase publicadora, después de la comprobación de nulidad y antes del disparo del evento, una llamada a System.Threading.Thread.Sleep(0), que permite ceder inmediatamente el control a otro hilo de ejecución que esté a la espera (por razones que aún no he podido aclarar, en la documentación para Silverlight se recomienda utilizar el valor 1 en vez de 0). A continuación se muestra un pequeño ejemplo que termina provocando (en mi máquina 🙂 la situación antes descrita.

using System;
using System.Threading;
using System.Windows.Forms;

namespace RaceWithEvents
{
    public class Form1 : Form
    {
        // ….

        private void iteration()
        {
            Publisher pub = new Publisher();
            Subscriber sub = new Subscriber(pub);

            // Cancel subscription from a new thread
            Thread t = new Thread( () =>
            {
                pub.MyEvent -= sub.HandleMyEvent;
            }
            t.Start();

            // Call the method that raises the event
            pub.DoSomething();

            // Join on the created thread
            t.Join();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < 10000; i++)
            {
                System.Diagnostics.Debug.WriteLine(i);
                iteration();
            }
        }
    }
}


Pop/rock tip: Por fin se me presenta la ocasión para hacer una referencia a mi banda favorita, Kansas (www.kansasband.com), que por primera vez incorporó un “toque americano” a la complejidad del rock sinfónico europeo de los ’70. Maestría interpretativa, rítmicas poco comunes con cambios frecuentes, la voz cristalina de Steve Walsh, pero sobre todo, para mí, letras que inspiran y sirven de guía para la vida – justo lo que les deseo que encuentren para sí mismos a mis lectores más jóvenes.

Curtain of iron” es un canto de esperanza dirigido a quienes sufren bajo sistemas totalitarios. Pertenece al álbum “Audio-Visions” (1980), el octavo de la banda y último que se grabó con la formación original. Comparado con best sellers como “Leftoverture” (1975), “Point of Know Return” (1977), “Two for the Show” (1978) o incluso “Monolith” (1979),  su éxito fue más que modesto, y anticipó el final de una era.

 

Octavio Hernandez

Desarrollador y consultor en tecnologías .NET. Microsoft C# MVP entre 2004 y 2010.

5 comentarios en “Qué problema tiene este código? (R)

  1. Gracias, Marino!
    Sigo sin encontrar por qué en Silverlight recomiendan usar 1 en vez de 0. Simple curiosidad, no más…

    Abrazo – Octavio

  2. Qué razón tienes con lo de estudiar la documentación, recientemente leí un artículo sobre la educación de los Filandeses, como sabes estos destacan frente a la mayoría de otros países europeos, uno de los motivos que achacaban era que ellos dedicaban mucho tiempo a la lectura y compresión en lugar, dicen que si eres capaz de comprender el problema ya tienes resuelto más del 50 % de este.

    Muy interesante estos post que nos hacen pensar, saludos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *