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.

 

Published 12/4/2009 10:48 por Octavio Hernández
Archivado en:
Comparte este post:

Comentarios

Monday, April 13, 2009 9:01 AM por Marino Posadas

# re: Qué problema tiene este código? (II)

Muy ilustrativo Octavio. Excelentes este tipo de ejemplos.

Marino

Monday, April 13, 2009 6:53 PM por Octavio Hernández

# re: Qué problema tiene este código? (II)

Gracias, Marino!

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

Abrazo - Octavio

Monday, April 13, 2009 7:07 PM por Juan Irigoyen

# re: Qué problema tiene este código? (II)

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.

Monday, April 13, 2009 7:49 PM por Octavio Hernández

# re: Qué problema tiene este código? (II)

Gracias, Juan!

Saludos - Octavio

Tuesday, April 14, 2009 2:49 PM por Octavio Hernández

# re: Qué problema tiene este código? (II)

Thursday, January 05, 2012 12:52 PM por Burbujas en .NET

# C# Básico: Eventos

Bueno… empieza el 2012: el último año de nuestra existencia si los maias no andaban errados (unos tíos