Tuesday 29 September 2009

Many threads make light work



Today is the final episode in my threading story. Next time, I'll talk about bread, and possibly Wicken Fen as well. The image on the left is of my coffee cup in front of its cafetière. The strange felt object is a coffee cosy, kindly made by my sister. Bet you don't have one of those!

Previous episode

As I hinted before, the separation of incoming stream from the list of words created, nice though it is as far as Object Orientation is concerned, makes even more sense if you are building up words from several streams and adding them to the same wordlist. Like trying to find out how many times the word "thriller" came up just after Michael Jackson's death (I'm trying to think of a one-word example!).

So, this time we'll start with an array of readers. This one has three. I amended the assignReaderContent function to give each one two different paragraphs from The Star by Arthur C Clarke (the first 6, in fact). I then created a single WordList and a list of WordListBuilder and a list of Threads. All Threads (there will be three) must share the same WordList. The function setupBuilderList is passed all three lists plus the WordList object, and associates them all together:

    static void setupBuilderList(List<WordListBuilder> bList,ICharReader[] rArray, List<Thread>threadList, WordList wList)
    {
        for (int i = 0; i < rArray.Length; i++)
        {
            bList.Add(new WordListBuilder(rArray[i], wList));
            WordListBuilder.buildWordListStarterOp listStarter = bList[i].buildWordListStarter;
            threadList.Add(new Thread(new ParameterizedThreadStart(listStarter)));
        }
    }
There is a lot going on here, and quite a few new parts of ASP.NET and the classes I have created to explain. Of course, at this point there are no Threads or WordListBuilders, so we have to make one builder to enclose each reader and associate each with a thread. We create a new builder for each reader, as before, passing the reader and the wordlist to the constructor. Then we declare a delegate, called buildWordListStarterOp for the builder in question to communicate with the rest of the operation.The starter op. in question is a member function called buildWordListStarter, which has to be passed an object (as does the delegate):

        public void buildWordListStarter(object state)
        {
            if (state is BuildWordListParams)
            {
                BuildWordListParams oP = (BuildWordListParams)state;
                oP.myWordListBuilder.buildWordList();
            }
        }

        public delegate void buildWordListStarterOp(object state);

In the setup function, a ParameterizedThreadStart object is required, so we can give the thread something to work with. The parameter has to be of type system.object. the BuildWordListStarter member function expects to be handed an object of the class BuildWordListParams:

class BuildWordListParams
    {
        public WordListBuilder myWordListBuilder;
        public BuildWordListParams(WordListBuilder a)
        {
            myWordListBuilder = a;
        }
    }

Of course, since we are only passing a list, and a list is already a system.object, it is not necessary to go to the trouble of declaring BuildWordListParams. On the other hand, it is now there, and if I wanted to pass something else, for example and instruction to ignore common words such as "the" and "it", I can just add them to this declaration and it remains just one object.

This class simply passes on an object of type WordListBuilder. Assuming all goes well (and I confess, we don't have any exception handling here) this means you can take the BuildWordListParams object and just call its buildWordList member function, the same one we have been using all along.

In the static void function, this buildWordListStarterOp is called listStarter and is then passed to the new Thread in its constructor. It is perhaps not necessary to delve into the workings of threads in general. I take the view that the ASP.NET team write Thread libraries so I don't have to.

We have a list of builders and a list of thread. We now set up the timer with just the same function as last time.

The last static function (startThreads) is to start each thread off, giving it the correct parameter as it goes:

    static void startThreads(List<thread> tList, List<wordlistbuilder> bList)
    {
        int i = 0;
        foreach (Thread t in tList)
        {
            BuildWordListParams p = new BuildWordListParams(bList[i]);
            t.Start(p);
            i++;
        }
    }

Each Thread is passed the corresponding WordListBuilder.

One more, rather important, point. If you have three threads able to access a resource, such as a list of words at the same time, you may well end up with rubbish results. The content of both member functions of WordList, AddNewWord and makeReportCalledBack are enclosed in a lock statement. This ensures only one thread (we hope, containing a builder) will access it at any one time. It will then be released for another thread to use.

Here are some screenshots:

Here, you can see that the main thread is number 9, and that numbers 10-12 have been assigned to the different builders.

Here, the main thread has finished, all builders have been assigned their char readers and all threads have been started.


We are getting the first report from thread 6, which is the Timer/WordList.













At this point most or all of the content has been read. I counted up instances of the word "that" in my 6 paragraphs and there are, indeed 10.

Here, two of the threads have hit the EndOfStreamException  and have been closed.
What I do find odd is that all reports cease at this point. The previous version kept making the same report over and over again. I do not understand this, and suspect I have introduced a bug.

AT some point I will try to get all of my files up here in a zip, so you can have them, if you want.

No comments:

Post a Comment