vtortola.Net

Junio 9, 2007

Modelo Asíncrono. Parte IV, sincronización.

Archivado en: .NET, C#, Modelo Asíncrono — vtortola @ 1:27 pm
Siguiendo con el modelo asíncrono, hoy voy a explicar una de las dos cuestiones básicas de sincronización, la primera el como serializar el acceso a un objeto de forma que solo un hilo pueda acceder a la vez usando la clase Monitor y la sentencia lock. Es importante saber controlar como nuestros hilos acceden a objetos y/ó datos en común, puesto que si un hilo modifica un objeto mientras que otro lo esta leyendo puede entorpecer ó incluso corromper los datos que ese hilo lee, hasta podría dejar el objeto en un estado inconsistente. Además veremos el uso de la palabra clave volatile y de otros métodos de sincronización no administrada. En el siguiente artículo explicaré como modificar un control visual desde un hilo distinto del de la GUI.

Tomando el ejemplo del articulo anterior, añado una colección List<int> ThreadIDs donde almacenaré el identificador del hilo en la función que ejecutamos de forma asíncrona, a su vez, se muestra el contenido de la colección cada vez (ahora entendereis el porque de hacer eso).

La sentencia lock asegura que su contenido solo será accedido por un hilo a la vez, eso nos asegura que nigún hilo intentará añadir un elemento a la colección mientras se esta recorriendo por el foreach, en caso contrario saltaría una InvalidOperationException.

private delegate void AsyncCallOut_(string p1, int p2, out StringBuilder parametroSalida);
private static AsyncCallOut_ AsynCallOut;
private static List<int> ThreadIDs = new List<int>();

static void Main(string[] args)
{

AsynCallOut = new AsyncCallOut_(AsyncFuncOut);
StringBuilder sb;
IAsyncResult ia=null;
for(int i=0;i<10;i++)
{

Console.WriteLine(“–> Lanzando tarea asíncrona {0}.”, i);
ia=AsynCallOut.BeginInvoke(
string.Format(“Prueba 1.{0}”, i), i, out sb, new AsyncCallback(FinAsyncFuncOut), string.Format(“objectstate_{0}”, i));

}

if (!ia.IsCompleted)
{

ia.AsyncWaitHandle.WaitOne();

}
Console.WriteLine(“Ultima tarea completada: {0}”, ia.IsCompleted == true ? “Si” : “No”);
Console.ReadKey();

}

private static void AsyncFuncOut(string p1, int p2, out StringBuilder sb)
{

sb = new StringBuilder();
sb.AppendFormat(
“{0} en AsyncFuncOut.”, p1);
Console.WriteLine(sb);

Random rndm = new Random(p2);
System.Threading.
Thread.Sleep(rndm.Next(5000, 10000));

lock (ThreadIDs)
{

ThreadIDs.Add(System.Threading.Thread.CurrentThread.ManagedThreadId);
foreach (int i in ThreadIDs)
{

Console.WriteLine(“threadID: {0}”, i);

}

}

}

private static void FinAsyncFuncOut(IAsyncResult IA)
{

try
{

StringBuilder sb;
AsynCallOut.EndInvoke(
out sb, IA);
Console.WriteLine(“Fin de AsyncFunc con resultado: ‘{0}’ con objectState : ‘{1}’.”,sb, IA.AsyncState);

}
catch (Exception Ex)
{

Console.WriteLine(“EX:{0}\n\n{1}”, Ex.Message, Ex.StackTrace);

}

}

Podriamos hacer lo mismo pero en lugar de usar la sentecia lock, usar la clase Monitor, en tal caso la función quedaria de la siguiente forma:

private static void AsyncFuncOut(string p1, int p2, out StringBuilder sb)
{

sb = new StringBuilder();
sb.AppendFormat(
“{0} en AsyncFuncOut.”, p1);
Console.WriteLine(sb);

Random rndm = new Random(p2);
System.Threading.
Thread.Sleep(rndm.Next(5000, 10000));

try
{

System.Threading.Monitor.Enter(ThreadIDs);
ThreadIDs.Add(System.Threading.
Thread.CurrentThread.ManagedThreadId);
foreach (int i in ThreadIDs)
{

Console.WriteLine(“threadID: {0}”, i);

}

}
finally
{

System.Threading.Monitor.Exit(ThreadIDs);

}

}

Notese la importancia de utilizar forma try-finally para asegurar que una vez que se bloquea el objeto se libere después, en caso contrario, si saltase una excepción por ejemplo en el bucle foreach nunca llegaria a ejecutarse el Exit() :) Como veis, usar lock es una forma resumida y determinista de usar Monitor, al igual que la sentencia using sería de hacar un try-finally y .Dispose ;)

Ahora vamos a hacer una prueba, eliminamos la “protección” de la función asíncrona de forma que cualquier hilo pueda modificar/acceder a la colección libremente. También aumentamos la cantidad de hilos asíncronos que se lanzaran por ejemplo a 100. La función quedaría así:

private static void AsyncFuncOut(string p1, int p2, out StringBuilder sb)
{

sb = new StringBuilder();
sb.AppendFormat(
“{0} en AsyncFuncOut.”, p1);
Console.WriteLine(sb);

Random rndm = new Random(p2);
System.Threading.
Thread.Sleep(rndm.Next(5000, 10000));

ThreadIDs.Add(System.Threading.Thread.CurrentThread.ManagedThreadId);
foreach (int i in ThreadIDs)
{

Console.WriteLine(“threadID: {0}”, i);

}

}

Efectivamente, conforme la colección engrosa su tamaño la probabilidad de que algun hilo intente modificarla mientras otro la recorre aumenta, hasta que por fin se da el caso y salta una InvalidOperationException. Espero que este pequeño ejemplo muestre la importancia del tema :)

La palabra clave volatile, indica que el campo declarada con ella puede ser accedido por varios hilos por lo que no se le aplican las optimizaciones del compilador, por lo que el valor más actualizado se encuentra siempre en la variable. De esta forma podemos proteger también un objeto/variable, pero yo siempre lo uso unicamente para tipos por valor y a la hora de implementar el patrón Singleton.

Otros métodos de sincronización son el Mutex y el Semaphore (que básicamente es un array de Mutex), pero son elementos que hace uso de recursos no administrados del sistema operativo, por lo que siempre que sea en nuestra aplicación .NET es más conveniente usar Monitor y lock, pero serian útiles para sincronizar entre dos aplicaciones disintas e incluso con otra aplciación que no sea .NET. Es importante tener en mente el GC.KeepAlive() pero asegurar de que el GC no elimina el objeto administrado que apunta al mutex del sistema operativo y por supuesto que controlamos bien el hecho de bloquear/liberar y no producimos un deadlock ;)

No Comments Yet »

Aún no hay comentarios.

Canal RSS de los comentarios de la entrada. URI para TrackBack.

Deja un comentario

Debes ser Sesión como para publicar un comentario.

Blog de WordPress.com.