System.Transactions y concurrencia

 

Hola a todos, llevo bastante tiempo desconectado y es que el verano me ha sentado mal y estoy muy perruno, pero bueno vamos a ver si podemos hacer algo para remediarlo. Hace tiempo conté un problemita que me surgió con los TransactionScope y su abuso.

 

Esta vez el problema estaba en que un proceso hacía una operación y en un momento llamaba a un hilo en background para procesar otro trabajo, pero todo tenía que estar dentro de la misma transacción o al menos que sólo se hiciese commit si todos los procesos acababan bien.

Bueno pues ahí empezaron mis problemas, porque cuando tú creas un TransactionScope por defecto el crea una transacción (ambient transaction que se llama) y la almacena en el Thread Local Storage (lo que viene siendo el TLS) del hilo en uso y esto hace que no se propague a los nuevos hilos que se generan. Y buscando por la red encontré el whitepaper de un “peaso” de crack que ya me dijeron en su tiempo, el gran Juval Lowy y expone la solución para estos problemas.

 

Os lo pongo aquí, pero realmente creo que es mejor que os bajéis el whitepaper y le echéis un vistazo que no es nada pesado y la verdad explica muy bien cómo funciona todo y el LTM y bueno, clarifica bastante…

 

En el tema de la concurrencia, os pongo un ejemplo de la clase que se ejecutaría sobre uno de los hilos generados

public class Obrero
{
public void Trabaja(DependentTransaction transaccion)
{
Thread thread = new Thread(Metodo);
thread.Start(transaccion);
}

public void Metodo(object transaccion)
{
DependentTransaction tran = transaccion as DependentTransaction;

Transaction antiguaTransaccion = Transaction.Current;

try
{
Transaction.Current = tran;

Debug.WriteLine("Esto tiene que estar dentro de una transacción... o algo similar :)");

tran.Complete();
}
finally
{
//Liberamos la transaccion que hemos pasado y volvemos a poner la transacción a la antigua
tran.Dispose();
Transaction.Current = antiguaTransaccion;
}
}
}

Aquí lo único ha observar, es que  a la clase se le pasa un objeto DepedentTransaction y es el que usa para hacer el Complete() una vez hecho el trabajo que tenga que hacer, en este caso un importantísimo Debug.WriteLine.

 

Desde el “padre” para invocar a esto sería un código similar a esto

using (TransactionScope scope = new TransactionScope())
{
//Vamos a recoger la transacción actual y generar un dependentTransaction a través
//del método DependentClone()
DependentTransaction transaccion = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);

//Aquí llamo a la clase que generar el hilo y hace otro trabajo transaccional
Obrero obrero = new Obrero();

obrero.Trabaja(transaccion);

Debug.WriteLine("El papi hace cualquiero tipo de trabajo transaccional");

//Este Complete() significa que en este instante se ejecutará el Commit de los hijos y del padre
//debido a la opción de DependentCloneOption que está a BlockCommitUntilComplete, esto quiere decir
//que no se hará ningún commit de los "hijos" hasta que el padre haga el Complete()
scope.Complete();
}

Bueno aquí las cosas importantes son el método DependentClone de la transacción actual y la opción de DependentCloneOption, lo demás no es más que pasarle a la clase que hemos creado antes, la transacción generada y que trabaje sobre ella.

 

Las distintas opciones de DependentCloneOption

Esto es un enumerado que tiene dos opciones, BlockCommitUntilComplete y RollbackIfNotComplete.

La opción BlockCommitUntilComplete, hace que no se ejecuten los commit de las transacciones dependientes hasta que la transacción “padre” haga el Complete(), esto en la practica hace que cuando en una transacción dependiente se haga un Complete(), la transacción se marca como “lista para hacer commit” pero no se ejecuta.

 

La opción RollbackIfNotComplete, hace que si el “padre” ejecuta el Complete() y existe alguna transacción dependiente que todavía no haya hecho un Complete() se generará una TransactionAbortedException, y además el hilo hijo seguirá trabajando para nada porque la transacción ya está abortada.

 

 

La verdad es que esto del System.Transactions tiene bastante miga, seguiré investigando cositas que aunque es una tecnología bastante antigua ya, todavía sigue dando mucha guerra en los proyectos porque no se utiliza correctamente, y por cierto os vuelvo a recomendar a todos leeros el whitepaper de Juval Lowy que mola…

 

Un saludo a todos.

Mario Ropero.

2 comentarios sobre “System.Transactions y concurrencia”

  1. Señor, gran artículo.

    Y una duda, por curiosidad, en C# cuál sería la mejor forma de hacer «una transacción» para actualizar una base de datos y modificar (o mover, copiar, crear) un fichero ??

    Es decir, que las operaciones de actualizar base de datos y acceso a disco fuesen ´como una sóla operación atómica. Nunca vi ningún ejemplo sobre este tema.

    Saludos y gracias.

Deja un comentario

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