2

I doing a small project to map a network (routers only) using SNMP. In order to speed things up, I´m trying to have a pool of threads responsible for doing the jobs I need, apart from the first job which is done by the main thread.

At this time I have two jobs, one takes a parameter the other doesn´t:

  • UpdateDeviceInfo(NetworkDevice nd)
  • UpdateLinks() *not defined yet

What I´m trying to achieve is to have those working threads waiting for a job to appear on a Queue<Action> and wait while it is empty. The main thread will add the first job and then wait for all workers, which might add more jobs, to finish before starting adding the second job and wake up the sleeping threads.

My problem/questions are:

  • How to define the Queue<Actions> so that I can insert the methods and the parameters if any. If not possible I could make all functions accept the same parameter.

  • How to launch the working threads indefinitely. I not sure where should I create the for(;;).

This is my code so far:

public enum DatabaseState
{
    Empty = 0,
    Learning = 1,
    Updating = 2,
    Stable = 3,
    Exiting = 4
};

public class NetworkDB
{
    public Dictionary<string, NetworkDevice> database;
    private Queue<Action<NetworkDevice>> jobs;
    private string _community;
    private string _ipaddress;

    private Object _statelock = new Object();
    private DatabaseState _state = DatabaseState.Empty;

    private readonly int workers = 4;
    private Object _threadswaitinglock = new Object();
    private int _threadswaiting = 0;

    public Dictionary<string, NetworkDevice> Database { get => database; set => database = value; }

    public NetworkDB(string community, string ipaddress)
    {
        _community = community;
        _ipaddress = ipaddress;
        database = new Dictionary<string, NetworkDevice>();
        jobs = new Queue<Action<NetworkDevice>>();
    }

    public void Start()
    {
        NetworkDevice nd = SNMP.GetDeviceInfo(new IpAddress(_ipaddress), _community);
        if (nd.Status > NetworkDeviceStatus.Unknown)
        {
            database.Add(nd.Id, nd);
            _state = DatabaseState.Learning;
            nd.Update(this); // The first job is done by the main thread 

            for (int i = 0; i < workers; i++)
            {
                Thread t = new Thread(JobRemove);
                t.Start();
            }

            lock (_statelock)
            {
                if (_state == DatabaseState.Learning)
                {
                    Monitor.Wait(_statelock);
                }
            }

            lock (_statelock)
            {
                if (_state == DatabaseState.Updating)
                {
                    Monitor.Wait(_statelock);
                }
            }

            foreach (KeyValuePair<string, NetworkDevice> n in database)
            {
                using (System.IO.StreamWriter file = new System.IO.StreamWriter(n.Value.Name + ".txt")
                {
                    file.WriteLine(n);

                }
            }
        }
    }

    public void JobInsert(Action<NetworkDevice> func, NetworkDevice nd)
    {
        lock (jobs)
        {
            jobs.Enqueue(item);
            if (jobs.Count == 1)
            {
                // wake up any blocked dequeue
                Monitor.Pulse(jobs);
            }
        }
    }

    public void JobRemove()
    {
        Action<NetworkDevice> item;
        lock (jobs)
        {
            while (jobs.Count == 0)
            {
                lock (_threadswaitinglock)
                {
                    _threadswaiting += 1;
                    if (_threadswaiting == workers)
                        Monitor.Pulse(_statelock);
                }
                Monitor.Wait(jobs);
            }

            lock (_threadswaitinglock)
            {
                _threadswaiting -= 1;
            }

            item = jobs.Dequeue();
            item.Invoke();
        }
    }

    public bool NetworkDeviceExists(NetworkDevice nd)
    {
        try
        {
            Monitor.Enter(database);
            if (database.ContainsKey(nd.Id))
            {
                return true;
            }
            else
            {
                database.Add(nd.Id, nd);
                Action<NetworkDevice> action = new Action<NetworkDevice>(UpdateDeviceInfo);
                jobs.Enqueue(action);
                return false;
            }
        }
        finally
        {

            Monitor.Exit(database);
        }
    }

    //Job1 - Learning -> Update device info
    public void UpdateDeviceInfo(NetworkDevice nd)
    {
        nd.Update(this);
        try
        {
            Monitor.Enter(database);
            nd.Status = NetworkDeviceStatus.Self;
        }
        finally
        {
            Monitor.Exit(database);
        }
    }

    //Job2 - Updating -> After Learning, create links between neighbours
    private void UpdateLinks()
    {

    }
}
Quality Catalyst
  • 6,531
  • 8
  • 38
  • 62
  • 2
    Would a `BlockingCollection` fit your needs? – mjwills Aug 19 '17 at 13:25
  • What do the different `DatabaseState`s mean exactly? – Kirill Shlenskiy Aug 19 '17 at 13:58
  • Possible duplicate of [BlockingCollection or Queue for jobs?](https://stackoverflow.com/questions/7161104/blockingcollection-or-queuet-for-jobs) – mjwills Aug 19 '17 at 23:05
  • DatabaseState is the the current state. Empty if i cannot access the first device. Learning while is looking up at each device routing table and jumping for each neighbour. Updating, after for creating the links between devices. I choose to create a state for this because I only know which neigbhour inteface is used for link with the current one, only after learning all about the neighbour. Exititng, would be the where I would signal every thread to stop. I will use this on a windows service. – Francisco Conceição Aug 20 '17 at 20:28

1 Answers1

0

Your best bet seems like using a BlockingCollection instead of the Queue class. They behave effectively the same in terms of FIFO, but a BlockingCollection will let each of your threads block until an item can be taken by calling GetConsumingEnumerable or Take. Here is a complete example.

http://mikehadlow.blogspot.com/2012/11/using-blockingcollection-to-communicate.html?m=1

As for including the parameters, it seems like you could use closure to enclose the NetworkDevice itself and then just enqueue Action instead of Action<>

Jeff
  • 35,755
  • 15
  • 108
  • 220