AjAgents: una nueva implementación

Hace ya un tiempo, escribí sobre mi proyecto AjAgents, describiéndolo, implementando algoritmos genéticos y otras demos mínimas. El proyecto se basa en el envío de mensajes a “agentes” (podría llamarlos actores, o algo así). Los agentes reciben los mensajes y los procesan uno por uno. Cada instancia de un agente no necesita manejar la concurrencia: los mensajes son encolados en una cola interna por cada agente (podría más adelante, reimplementar esto usando una cola compartida entre varios agentes, o consumida por un pool de threads fijo que vaya consumiendo trabajo pendiente de esa cola, derivándo cada uno al agente destino).

Pero mis anteriores implementaciones se basaban en el uso de Reflection, o, alguna vez, en la librería CCR de concurrency de Microsoft Robotics (ver mis anteriores posts para más detalles). Ahora, tomo otro camino: un agente es un wrapper alrededor de un objeto normal. El código fuente puede ser bajado desde mi  proyecto AjCodeKatas Google Code, en el directorio trunk/AjAgents.

La interface principal, clave, es:

    public interface IAgent<T>
    {
        void Post(Action<T> action);
    }


La nueva idea: usar una clase genérica. T es el tipo de nuestro tipo “clásico” (que no es un agente), y Post es el método para invocar una acción sobre el objet interno: una acción que es una “rutina” que recibe un parámetro instancia de tipo T. De esta manera, le indicamos al objeto interno. Si éste tiene un método:



inner.DoSomething(parameter);



puede ser invocado como un mensaje desde el agente como:



agent.Post(x => x.DoSomething(1));



Esta es la implementación base de agente:



    public class Agent<T> : IAgent<T>
    {
        private T instance;
        private AgentQueue<T> queue;
        private bool running;
        public Agent(T instance)
        {
            this.instance = instance;
        }
        public void Post(Action<T> action)
        {
            if (!this.running)
                this.Start();
            this.queue.Enqueue(action);
        }
        private void Start()
        {
            lock (this)
            {
                if (this.running)
                    return;
                this.queue = new AgentQueue<T>();
                Thread thread = new Thread(new ThreadStart(this.Execute));
                thread.IsBackground = true;
                thread.Start();
                this.running = true;
            }
        }
//...
    }


Noten que el método Post encola la acción en la cola interna del agente. El proceso de esta cola está en el método .Execute (que no se muestra arriba). La cola es una cola bloqueante (sé que hay algo así en .NET 4.0, pero escribí mi implementación para poder ejecutar todo esto en 3.x, o aún 2.x):



    public class AgentQueue<T>
    {
        private Queue<Action<T>> queue = new Queue<Action<T>>();
        private int maxsize;
        public AgentQueue()
            : this(100)
        {
        }
        public AgentQueue(int maxsize)
        {
            if (maxsize <= 0)
                throw new InvalidOperationException("AgentQueue needs a positive maxsize");
            this.maxsize = maxsize;
        }
        public void Enqueue(Action<T> action)
        {
            lock (this)
            {
                while (this.queue.Count >= this.maxsize)
                    Monitor.Wait(this);
                this.queue.Enqueue(action);
                Monitor.PulseAll(this);
            }
        }
        public Action<T> Dequeue()
        {
            lock (this)
            {
                while (this.queue.Count == 0)
                    Monitor.Wait(this);
                Action<T> action = this.queue.Dequeue();
                Monitor.PulseAll(this);
                return action;
            }
        }
    }


Apliqué TDD para el desarrollo de todas estas clases, un ejemplo de test:



        [TestMethod]
        public void InvokeIncrement()
        {
            ManualResetEvent handle = new ManualResetEvent(false);
            Counter counter = new Counter();
            Agent<Counter> agent = new Agent<Counter>(counter);
            agent.Post(c => { c.Increment(); handle.Set(); });
            handle.WaitOne();
            Assert.AreEqual(1, counter.Count);
        }


Mucho del código fue derivado (bueno… casi copy and paste ;-) de mi trabajo previo con AjSharp (channels, agentes, queues, goroutines… ).



Tengo preparada una demo “prueba de concepto” usando esta nueva implementación de AjAgents. Es mi “clásico” ejemplo de Web Crawler. Pero eso es tema para otro post.



Nos leemos!



Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

This entry was posted in 12677, 1389, 7338. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>