Hora de volver a trabajar... asi que toca quitarle el polvo al Visual Studio que tenia abandonado ya estos meses, la buena vida se acaba... xD
En determinadas ocasiones, necesitamos lanzar un cierto número de ejecuciones en paralelo y esperar a que terminen todas para devolver el resultado final. Existen diversas formas de hacerlo, y yo voy a poner la que, después de darle un par de vueltas... me parece la mejor y más sencilla, ... se puede hacer aún mejor, pero no se si más sencilla... y no se si lo que se puede mejorar merece la pena en cuanto a la complejidad que añade.
Un ejemplo práctico, este método pertenece a una clase que estoy programando para realizar pruebas de estres y carga, la clase tiene una lista de clientes y cuando se ejecuta esta función ejecuta el delegado que se pasa como argumento en paralelo en todos los clientes a la vez, recolecta las excepciones capturadas y las devuelve como una lista. Por su puesto, tiene que esperar a que terminen todas las pruebas en paralelo para poder devolver la lista :P
private List<Exception> test(Int32 Times, Int32 Interval, TestDlg_ Test)
{
List<Exception> errors = new List<Exception>();
List<IAsyncResult> working = new List<IAsyncResult>();
lock (working) // Bloqueo hasta que lanze todos los clientes
{
foreach (IImApplicationClient client in this.Clients)
{
working.Add(Test.BeginInvoke(client, Times, Interval,
delegate(IAsyncResult IA)
{
try
{
Test.EndInvoke(IA);
lock (working)
{
working.Remove(IA); // Lo elimino de la lista,
if (working.Count == 0) // y si era el último...
Monitor.Pulse(working); // envio pulso para liberar
} // la espera
}
catch (Exception ex)
{
IImApplicationClient asynClient =
IA.AsyncState as IImApplicationClient;
lock(errors)
errors.Add(new Exception
(client != null ? asynClient.Id : "Id unknown.", ex));
}
}, client));
}
if (working.Count > 0) // Si se han lanzado clientes...
Monitor.Wait(working); // bloqueo en espera.
}
return errors;
}
El planteamiento es el siguiente:
- Se usa una lista para tener controladas las llamadas asíncronas.
- Cada vez que se lanza una llamada asíncrona, el IAsyncResult resultante se añade a la lista.
- Se bloquea la lista hasta que todas han sido lanzadas, y si se ha lanzado más de una, se ejecuta un Monitor.Wait para que se detenga la ejecución ahí hasta que se realize un Monitor.Pulse.
- Cada vez que una llamada asíncrona termina, se elimina su IAsyncResult de la lista.
- Si el IAsyncResult es el último que quedaba, se realiza un Monitor.Pulse para que la ejecución pueda seguir puesto que ya finalizaron todas las llamadas asíncronas.
Sugerencias, como siempre, bienvenidas :D