En post anteriores:
Canales y GoRoutines en AjSharp (Part 1)
Canales y GoRoutines en AjSharp (Part 2)
describí la implementación de gorutinas (goroutines, como en el lenguaje Go de Google), y canales en mi intérprete AjSharp. Al final del año que pasó, escribí una prueba rápida, implementando los mismos conceptos, pero para ser consumidos esta vez desde C# directamente. Pueden ver el código en
http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjConcurr
Primero, porté la clase Channel:
public class Channel
{
private AutoResetEvent sethandle = new AutoResetEvent(false);
private AutoResetEvent gethandle = new AutoResetEvent(false);
private object value;
public void Send(object value)
{
this.gethandle.WaitOne();
this.value = value;
this.sethandle.Set();
}
public object Receive()
{
this.gethandle.Set();
this.sethandle.WaitOne();
object result = this.value;
return result;
}
}
El código tiene una clase estática GoRoutines, con métodos como:
public static void Go(Action action)
{
Thread thread = new Thread(new ParameterizedThreadStart(GoRoutines.RunAction));
thread.IsBackground = true;
thread.Start(action);
//ThreadPool.QueueUserWorkItem(new WaitCallback(RunAction), action);
}
public static void Go(ITask task)
{
Thread thread = new Thread(new ParameterizedThreadStart(GoRoutines.RunTask));
thread.IsBackground = true;
thread.Start(task);
//ThreadPool.QueueUserWorkItem(new WaitCallback(RunTask), task);
}
Pueden lanzar una Action (lo que es en el framework delegate System.Action<>), en un nuevo thread (traté de hacerlo poniendo la tarea en la cola de ThreadPool, pero, no sé por qué, el rendimiento pasó a ser muy bajo; mi intento quedó en comentarios del código de arriba).
Cuando una acción recibe parámetros, se encapsulan los dos en una Task<>, como esta que recibe dos parámetros:
public class Task<T1, T2> : ITask
{
private Action<T1, T2> action;
private T1 parameter1;
private T2 parameter2;
public Task(Action<T1, T2> action, T1 parameter1, T2 parameter2)
{
this.action = action;
this.parameter1 = parameter1;
this.parameter2 = parameter2;
}
public void Run()
{
this.action(this.parameter1, this.parameter2);
}
}
(hay clases Task para uno, dos o tres parámetros).
Pueden invocar GoRoutines.Go directamente, especificando una acción y sus parámetros:
public static void Go<T1, T2>(Action<T1, T2> action, T1 parameter1, T2 parameter2)
{
Go(new Task<T1, T2>(action, parameter1, parameter2));
}
Podemos escribir esta invocación usando expresiones lambda:
[TestMethod]
public void RunGoRoutineWithTwoParameters()
{
int i = 0;
AutoResetEvent handle = new AutoResetEvent(false);
GoRoutines.Go((x, y) => { i = x + y; handle.Set(); }, 2, 3);
handle.WaitOne();
Assert.AreEqual(5, i);
}
Con todo esto implementado, escribí una aplicación de consola AjConcurr.Primes, reimplementando el ejemplo de números primos de mi anterior post:
Channel numbers = new Channel();
GoRoutines.Go(() => { for (int k = 2; ; k++) numbers.Send(k); });
Channel channel = numbers;
int prime = 0;
while (prime < 1000)
{
prime = (int)channel.Receive();
Console.WriteLine(prime);
Channel newchannel = new Channel();
GoRoutines.Go((input, output, p) =>
{
while (true)
{
int number = (int)input.Receive();
if ((number % p) != 0)
output.Send(number);
}
}, channel, newchannel, prime);
channel = newchannel;
}
Me gusta ver este código formateado en pastie:
http://pastie.org/761916
Próximos pasos:
- Mejorar Channel para soportar múltiples productores y consumidores de valores que operan de forma simultánea sobre el mismo canal.
- Agregar soporte de Futures
- Agregar características de reactive programming (sé que está el Reactive Framework, quería experimentar algo con código propio).
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez