THREADS2 id 2053271 Nieznany


Document :: Making Use of Threads

Author :: Jon Jenkinson

From â€Ĺ›The Bits...” the c++ Builder Information & Tutorial Site

http://www.cbuilder.dthomas.co.uk

Contact :

Main Site : forgot@mcmail.com

Newspages : bitsnews@mcmail.com

Comments : comments@mcmail.com

Editors:

Components : Alan D. Mills, millsad@tgis.co.uk

Tutorials : Kris Erickson, kson@istar.ca

Others bits: Jon Jenkinson, jon.jenkinson@mcmail.com

Legal Stuff

This document is Copyright © Jon Jenkinson, December 1997

The PDF distribution release of this document is Copyright ©"The Bits...”, December 1997

You Can,

Redistribute this file FREE OF CHARGE providing you do not charge recipients anything for the

process of redistribution, or for the document itself.

You Can,

Use this document for educational purposes.

You Must Gain Permission, *Initial contact forgot@mcmail.com*

If you choose to redistribute this document in any way, (book/CD/Magazine/Web Site etc.). (note, this is to avoid any possible conflict with commercial work the author may be undertaking, though it is unlikely permission will be refused).

You Must Gain Permission, *Initial contact forgot@mcmail.com*

If you wish to use all or part of this document for commercial training purposes.

You Cannot,

Under any circumstances alter this document in any way without the express permission of the author or â€Ĺ›The Bits...”.

You Cannot,

Receive **any** monies for this document without the prior consent of the author.

You Cannot,

Take any of the ideas presented here and produce a product for distribution without the express consent of the author.

In plain words, if you wish to do anything other than read and print this document for you own individual use, let us know so we can track what’s going on:)

No liability is accepted by the author(s) for anything which may occur whilst following

this tutorial



This project has been built and tested under Win95, (4.00.950a not OEMR2), and using

CBuilder Standard, (both patches). It has not been tested under later releases of Win95,

NT nor different versions of CBuilder, and whilst no side effects should occur, follow

this at your own risk.

Making Use Of Threads

This document has come about after a little â€Ĺ›quick” project I wrote to do a simple but time consuming job for myself. Here we will write a, (very simple), duplicate file finder, which will be multi-threaded to enable more than one check to be made at one time. Once you’ve gone through this there will be loads of enhancements you can think of, but I stopped when it did what it needed to do.

The major thing we’ll look at here is making an advanced use of threads to carry out all the actual work. The reason for doing this is that the process is a long one. We’re doing a full file comparison, not just a check of the filename/date/size, which takes time. (In fact, I don’t even bother checking the file sizes!). Before reading this, check out the document on Threads available from the site. That document gives you an overview of what a thread is, and how it works, but this one will show you how to make a thread do some real work to make your applications more effective.

Colour Coding:

Information displayed in this colour is information provided by my copy of BCB.

Information displayed in this colour is information you need to type.

Information displayed in this colour is information which is of general interest.

********************************************************************************

Whilst not required, I use TSlidePanel available from the sites component sections to

create splitter windows for the display within this project. If you have your own

component or method for doing this use that, or, you don’t even have to split the

windows at all.

********************************************************************************

Underlying Principals

As mentioned above, I’m expecting you know what a thread is, and similarly how to use CBuilder. Once you’ve read through this, you’re unlikely to learn much more than the Threads tutorial above, but you will see how to use threads in a real application, and some of the things that entails. Also to track the threads we’ll be using a TList object, and that wasn’t as easy as I first thought :)

How to Make a threaded Duplicate file finder.

Before diving in, a bit of product design. The problem this little application solves is as follows.

Having four hard drives, a Zip drive, and innumerable floppies, it becomes very easy to have duplicate files littered all over the place. This isn’t normally too much of a problem, they have the same name, size, etc., but then you get a fool like me who has a penchant for renaming files when backing them up to other local drives. Similarly, when searching out components or information from the web, I download lots of files at once, frequently all on the same subject.

Web site curators are another lot who love to rename files to fit their own structure, for instance this document may end up on your own drives as it’s original â€Ĺ›thdupli.pdf” as well as having been renamed by another curator to â€Ĺ›athreads.pdf”.

In all the above cases, you don’t wish to manually search through each file checking for duplicates, you just wish to remove those duplicates. That is what this program does. It ignores file names and sizes, (except for one particular case), and does an exact match on each file presented it. Then a list is produced of those files which it believes are identical, (though this could be easily produced as a list for deletion, or simply deleted anyway.

Now the special thing here is the file matching, which isn’t hard, just takes a great deal of time.

The commercial software tend to lend themselves to matching filenames/sizes/dates etc., but from my own experience frequently produce erroneous results, (especially when checking down lists of readme files:). So this is a simple tool to check the files exactly. (*Note, the source code for this application is being put into a Global Project, so if you see an enhancement, follow that project to create the greatest file cleaner of all time*).

Step One : Create The Application Windows.

My layout led me to needing two application windows, the first for choosing the files and directories to search, and the second for displaying the results produced.

First, the application window, (note, uses TSlidePanel, use your own method if you prefer).

(a) Create an new application with File|New, and set the forms caption = Dupli (b) drop a TPanel on the form, Panel1, set properties, Align = alTop, Alignment = taLeftJustify, both bevels to bvNone, and Caption = Dupli.

(c) drop another TPanel on the form, Panel2, set properties, Align = alRight, Alignment =

taCenter, both bevels to bvNone

(d) another TPanel, Align = alClient, BevelOuter = bvLowered (e) drop a TSlidePanel on the form, Align = alLeft, BevelOuter = bvLowered, HandlePosition =

hpRight, SlideType = stLine

(f) drop another TSlidePanel on the first one, Align = alBottom, BevelOuter = bvRaised, HandlePosition = hpTop, SlideType = stLine

(g) Place a TPanel, (Panel4), to the right of the first SlidePanel, Align = alClient, BevelOuter =

bvNone

(h) and another TSlidePanel onto Panel4, Align=alBottom, BevelOuter = bvRaised, HandlePosition = hpTop, SlideType = stLine.

Okay, just to check, you have a panel at the top, one to the right, a pair of slidepanels on the left, and one on the right. (Check the screenshot at the end of the next step).

Step Two : Adding the Bits.

Now we’ll add the components which will make up the rest of our main window, in effect two drive explorers and some buttons. (Again check the screen shot below:) (a) Onto Panel1, drop the following, two TDriveComboBox’s, Named, DC1 & DC2, two TEdit’s named fil1 & fil2.

(b) Place a TDirectoryListBox on the SlidePanel on the left, named DL1, Align = alClient (c) Place another TDirectoryListBox onto Panel4 on the right, named DL2, Align = alClient (d) Place a TFileListBox on the second SlidePanel on the left, named fl1, Align = alClient (e) Place a TFileListBox on the final SlidePanel, named fl2, Align = alClient, Mask = *.*

(f) Link the boxes together, DC1:DirList = DL1, DC2:DirList = DL2, DL1:FileList = fl1, DL2:FileList = fl2

(g) Place three TBitBtn’s on the panel right hand panel, named GoBtn, StopBtn, ClearBtn. For



the GoBtn, set (in this order), Kind = bkAll, ModalResult = mrNone, Caption = &Go. For the StopBtn, Kind = bkNo, ModalResult = mrNone, Caption = &Stop. For the ClearBtn, Kind =

bkCancel, ModalResult = mrNone, Caption = &Clear. (This basically gives us some quick glyphs on our buttons:).

Your application window should look something like this, The layout doesn’t matter too much, this one simply suits my purposes.

Step Three : Adding the other modules.

Before saving our project, lets add our other units.

(a) Create a new form, File|New Form

(b) Create a new Thread, File|New, choose Thread Object from the list, and give it the class name JSearch.

Now lets save our project with File|Save Project As, choose your location, save unit1 & unit2, but name the threads file as JSearch.

Step Four : Form2.

Here we’ll fill out form2 with our output display, and also enable the user to save the output.

(a) Drop a TPanel on Form2, Align = alBottom

(b) Drop another TPanel on the form, Align = alClient (c) Place a TSaveDialog on the form, (it doesn’t matter where), Name = SD.

(d) Place a TRichEdit on each panel, Align = alClient, WordWrap = false, ScrollBars = ssBoth for each of them, name the one at the bottom of the form r2, and the one at the top of the form r1.

(e) Place a TMainMenu onto the form, (again it doesn’t matter where), Name MainMenu1.



(f) Double Click the TMainMenu on your form, and create the following Menu display, (g) Name Duplicate List, SaveList

(h) Name Debug Window, SaveDebug

(note, I’ve pinched this window from part of the larger project I’m working on, so ignore the grayed about)

(i) Double click the Duplicate List and add the following into it’s OnClick handler,

//---------------------------------------------------------------------------

void __fastcall TForm2::SaveListClick(TObject *Sender)

{

if(SD->Execute())

{

r1->Lines->SaveToFile(SD->FileName);

}

}

//---------------------------------------------------------------------------

(j) Double click the Debug Window, and add the following into it’s OnClick handler

//---------------------------------------------------------------------------

void __fastcall TForm2::SaveDebugClick(TObject *Sender)

{

if(SD->Execute())

{

r2->Lines->SaveToFile(SD->FileName);

}

}

//---------------------------------------------------------------------------

You’ve now finished with Form2:)

Step Five : The Buttons.

Honest, we get to the interesting stuff in a moment, but for now lets make our buttons do something. Well actually, here we do something interesting. We’re going to use a TList class to track our threads. This is needed because we’re going to enable the user to fire threads off whenever they want, and we’re not going to limit in any way the number of searches they can start. In practice, most (old:P120, 32meg) PCs like mine will start becoming unresponsive once we’ve got 8 or 9 threads going, but I’ve no idea what will happen on the newer and faster machines.

So what is a TList. It’s a container for pointers, simple:). To enable the TList we need to create

an instance of one, so into Unit1.h, place the following two lines in red, (the Counter is something we’ll come to later.

class TForm1 : public TForm

{

__published:

// IDE-managed Components

//

private:

// User declarations

TList *ThreadList;

public:

// User declarations

__fastcall TForm1(TComponent* Owner);

int Counter;

};

//---------------------------------------------------------------------------

The first thing to notice is that we’ve placed our TList within the private section of our class’

declaration. We don’t want to be able to access this List from outside our class, so it is good practice to keep something so important private.

And it is important. This list is going to track all our threads, and whilst we’ll allow our thread to terminate itself, and we’ll allow our application to terminate, (which consequently terminates all threads), we also wish to be able to terminate all the threads ourselves, and thus we need an accurate record of which threads are running.

What our list will do is provide us with control over our threads. When we launch a thread, we will record a pointer to that thread within our list, and whilst we don’t care if the thread finishes itself, or the user terminates, we will come to a point, (our stop button), when the threads need to be terminated. We pack our list, (remove all null pointers), and then delete(terminate) our threads.

(And here is a question for the Geni out there. The logic I’ve used for this application whilst perfect, is flawed. It is possible that when we’re scrolling through our list terminating our threads, that a thread will finish itself, and thus present us with a null pointer. Whilst highly unlikely, it can happen, though it hasn’t happened to me yet ! Solutions ?:) (a) Okay, first of all let’s allocate our list. In Form1’s OnCreate handler, the following.

//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)

{

//Create the TList to hold pointers to the threads;

ThreadList = new TList;

Counter = 0;

Panel2->Caption = Counter;

}

//---------------------------------------------------------------------------

(b) In the OnClose handler,

//---------------------------------------------------------------------------

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)

{

delete ThreadList;

}

//---------------------------------------------------------------------------

What we’ve done here is allocate and de-allocate our list. The beauty of TList is that you don’t have to worry about pointer sizes or anything else too complicated, just let it manage itself. (If you’re interested, the help describes TList badly, how it auto allocates memory in blocks etc., but for our purposes, and most that I can imagine, you can let TList take care of such things itself).

The other two lines in our OnCreate function are simply to set up and display our counter.

(c) The Clear Button, will clear our output windows, and our counter, so the following code in ClearBut’s on click handler

//---------------------------------------------------------------------------

void __fastcall TForm1::ClearBtnClick(TObject *Sender)

{

Form2->r1->Lines->Clear();

Form2->r2->Lines->Clear();

Counter = 0;

Panel2->Caption = Counter;

}

//---------------------------------------------------------------------------

Note here that hitting clear will not stop any running threads. Depending on how our use wishes to use our application, they can go through the richedit’s and cut paste, delete and so on. They may choose to delete (clear), whilst the thread is running, which is what we allow them to do.

(If you wish to stop the threads with a hit of the clear button, put a call into the stop button click handler, i.e., StopBtnClick(Sender);)

(d) The Go button. This button will create a thread, and our list will record this thread. There are two ways of starting threads, and both can be enabled here. Put the following code into the Go Buttons OnClick handler.

//---------------------------------------------------------------------------

void __fastcall TForm1::GoBtnClick(TObject *Sender)

{//fire off the threads to do the searching

//JSearch *NewThread;

fl1->ItemIndex = 0;

fl2->ItemIndex = 0;

Form2->Show();

//add our thread pointer to our list

ThreadList->Add(new JSearch(false));

//the above fires it off and let's it run:)

//do the following if choose to start suspended

//ThreadList->Add(new JSearch(true));

//NewThread = reinterpret_cast (ThreadList->Items[ThreadList->Count-1]);

//NewThread->Resume();

}

//---------------------------------------------------------------------------

Okay, there’s a lot there:). I’ve chosen to fire my thread directly, (that is not to create it in a suspended manner). When we look at our thread later, you’ll see that everything we need to start it is put in place by the thread, so we shouldn’t need to pause to allow something else to catch up. It is however considered good practice to create a thread in suspended mode and then only allow it to run when everything is in place, which is why I’ve presented both methods here.

However, as neither we, nor the application has anything else to do between our threads creation and our threads run, the lines above which are commented out are redundant, so I’ve not bothered with them.

When we enter this function, we make the output form visible, (it doesn’t matter if it’s already on show),

and then launch our thread, and add it to our list. The line, ThreadList->Add(new JSearch(false)); does this in one go. If you’ve read the Threads tutorial, you’ll know that to launch a thread, you declare a pointer of the correct type, and allocate the pointer with a call to the thread’s constructor. In effect, the above line could have been typed in the following manner,

JSearch *MyPointer;

MyPointer = new JSearch(false);

ThreadList->Add( MyPointer);

which is actually a lot more complicated. Remember that a TList keeps track of pointers, so it expects a pointer when you do an add. Thus, the single line ThreadList->Add(new JSearch(false)); does the following,

ThreadList->Add(........) creates our pointer, and the ...new JSearch(false)... returns a pointer to our thread, and allocates all required for our threads constructor.

A small thing to note here. If our thread fails to create for any reason, the pointer it returns is NULL, which we store regardless. This is not a problem for us, for when we reference the list we remove NULL pointers first, but it’s something to be aware of.

(For information. If we had chosen the create suspended mode for our thread, it becomes a little more complicated. Create a pointer of the type we want, JSearch *NewThread; We add our pointer to the list in the same manner as above, with the call to, ThreadList->Add(new JSearch(true)); which creates the pointer, stores it in our list, allocates and creates our thread, but suspends it. We then need to resume our thread, which requires us to access the pointer.

This is done with the following line, NewThread = reinterpret_cast (ThreadList-

>Items[ThreadList->Count-1]); which brings our pointer back from the list, and casts it back to the type we expect. Note the use of the TList’s Count function, we’ve just added a thread, so the count goes up, but it’s a 0 based list, so we need count-1. Finally we resume our thread.

NewThread->Resume();

Now then, taking the argument of the single line allocation, the above four lines and expanding them, would become the following four lines

JSearch *NewThread;

NewThread = new JSearch(true);

ThreadList->Add( NewThread);

NewThread->Resume();

which is probably easier. The method you choose is really dependant on whether you need to do anything with your thread before you let it start, and is best left to the individual cases. I’ve included all the options so at least you know how it can be done, though in our case as we don’t need to do anything, we can do it all in one line:)

Okay, now this will actually create and run a thread which doesn’t do anything, (we will put the thread code in place soon:). The next thing we need to look at is the forced termination of our threads, the StopBtn.

(e) Place the following code in the stop buttons OnClick handler.

//---------------------------------------------------------------------------

void __fastcall TForm1::StopBtnClick(TObject *Sender)

{

int x;

JSearch *TempPtr;

//Terminate the thread(s)

ThreadList->Pack();

//remove empty pointers

if(ThreadList->Count != 0)

{

for(x = 0; x < ThreadList->Count; x++)

{



TempPtr = reinterpret_cast(ThreadList->Items[x]); TempPtr->Terminate();



}

TempPtr->Terminate();

}

}

//---------------------------------------------------------------------------

(for those who are questioning the for loop above, I know there is a flaw in the above for loop, but I’m damned if I can mend it, however the above works flawlessly:) Here we run through our list and terminate the threads that are running. The first thing we do is call TList’s Pack function, which as mentioned earlier removes all NULL pointers from the list.

This means that if a thread has finished running since the go button was hit, it’s space in our list is removed by this call. We then check to see if there are actually any threads left running by checking the count.

(A couple of things to note here. Our TList has two things of interest. It has a capacity, and a count. The capacity is best left to the TList to handle, but in effect, we could have a list capacity of 8 here, and a count of 0. However, before the pack, we could have a capacity of 8, with a single pointer to a thread at position 8, that is seven null pointers before the pointer we’re interested in. What pack actually does is remove all the null pointers and â€Ĺ›compress” the list so that 0 to count produces valid pointers of the type we want. Pack also reallocates the list capacity, but we really don’t need to know that, we’re only interested in the â€Ĺ›real” pointers that are there.

The second thing to note is that I’ve made no attempt of checking the pointer type returned. In our use of TList, we’re assuming that all the items stored in the list are going to be of type JSearch, (and it should only contain such pointers as that’s all we ever send it). However, a TList can store pointers of any type, in any order. You could for instance have a TList which contained the following pointers,

Items[0] = int *

Items[1] = char *

Items[2] = JSearch *

Items[3] = Null

Items[4] = JSearch *

Now our call to pack will remove our NULL, but what to do if you have mixed pointers. Well, that’s your problem, because it is my opinion that you’re asking for trouble if you do such a thing. In such a case simply maintain lists for each pointer type, as if you were maintaining a keyed database table. That is when you remove an item from one list, you remove it from another list.

Finally, as mentioned earlier, there is no check here on what to do if a thread terminates whilst we’re in this for loop. It is possible, though I haven’t managed it yet. My gut reaction is that prior to our thread terminating, we call a function which searches our list for it’s own pointer and removes it, but that seems to me like taking a chainsaw to snap a match, and also brings in a

problem of what to do if you remove the pointer from the list, again during the loop. To me, this would bring in the possibility of trying to terminate a thread which is in the process of trying to terminate itself by removing the pointer you’re using. Hmmm....note that this is highly unlikely, but be aware of the possible problem.)

(f) And before we write our thread code, the following two handlers for our display.

//---------------------------------------------------------------------------

void __fastcall TForm1::fil1Exit(TObject *Sender)

{

fl1->Mask = fil1->Text;

}

//---------------------------------------------------------------------------

void __fastcall TForm1::fil2Exit(TObject *Sender)

{

fl2->Mask = fil2->Text;

}

//---------------------------------------------------------------------------

These basically update the filelists with the chosen mask(s) supplied by the user when they exit the edit boxes. (Note on masks, â€Ĺ›*.jpg”, is valid, as is â€Ĺ›*.jpg;*.gif”, so they can specify multiple wildcards:)

Step Six : The Search Thread.

So far we haven’t run the application, and the reason is simple, it wouldn’t do anything if we did.

The Search Thread, JSearch, exists, but currently does absolutely nothing, other than start and stop almost instantly. Now we’ll put some meat into our thread.

One of the many problems in comparing files is that it is in effect a single process. You compare file 1 with file 2 and file.... until you’ve compared them all. In a single thread application it can be difficult to maintain control of your application, you effectively need to wait for the search to finish, or check a flag which may, or may not be accessible. The second major difficulty is that you can only run one search at a time. Within a thread, this is all much easier. With a little foresight you can use the same thread to do all the searches you need, and you can control the threads from your main application thread. In other words, no matter what happens in your thread, your application is always accessible.

(During the development of this thread I frequently made logic errors, some of which brought down Windows :(, however, much to my surprise, my main application thread always continued to work completely, and I could fire up new threads to cause another GPF even though I had outstanding violations in some current threads:)

So to work with this thread we need to think about what it is we’re trying to do. We wish to take a list of files, and compare each file with another list of files. Now we have our filelists within our current main application window, and we could use these, but this would create problems, especially if the user changes the filelist whilst our thread is running, suddenly we’re not comparing the list we started to compare. So we need to make our thread create and maintain all the information it requires to do the search it’s being asked to do itself.

The information we need for a search is quite simple, the list of files to compare against another list of files, two lists. Now on our main display we have these two lists, fl1 and fl2, so when our user hits the Go button we need to copy these into our thread. As a filelist maintains its lists of files a TStringList, we’ll make life easy and maintain two TStringLists of our own.

Before we dive in and create these as items in the thread’s Execute() function, we need to give a little thought in the way threads in CBuilder work. Whenever you’re doing something which involves talking to another thread within your application you must synchronise the calls, to

ensure that nothing else is trying to do the same. In CBuilder threads this is done for you automatically by using the Synchronize function, and to this end we need to ensure that any variable set by referencing any other thread, (our main app window for instance), needs to available to all parts of our thread class.

(a) Edit JSearch.h and put in the following declarations, (in Red).

//---------------------------------------------------------------------------

#ifndef JsearchH

#define JsearchH

//---------------------------------------------------------------------------

#include

//---------------------------------------------------------------------------

class JSearch : public TThread

{

private:

String CheckMask;

String AginMask;



String CheckDirec;

String AginDirec;

TStringList *Files;

TStringList *AgainstList;

String Name1;



String Name2;

void __fastcall SetFile();

void __fastcall SetAgin();

void __fastcall UpdateDisplay();

void __fastcall Error();

void __fastcall UpdateCounter();

void __fastcall UpdateLog();

protected:

void __fastcall Execute();

public:

__fastcall JSearch(bool CreateSuspended);

};

//---------------------------------------------------------------------------

#endif

Now that probably strikes you as a lot of declarations:). Let me explain. From the top, CheckMask and AginMask are to help us speed the search, CheckDirec and AginDirec are holders for the two directories being used, Name1 and Name2 will be the actual filenames for each check, and the two important ones, Files and AgainstList, are our TStringLists which will hold the lists of files to check.

The functions are all declared as private, though they could be in the public section. The reason for this is that within a thread any function which talks to other threads, (anything within your application), should be done within a call to the Synchronize() function. As such, any such calls are always going to be private to your class, as your class is the only thing that should call them.

(b) Setting our variables and lists. In the Execute() function, put the following

//---------------------------------------------------------------------------

void __fastcall JSearch::Execute()

{

//---- Place thread code here ----

int Loop1, Loop2;

FILE *one, *two;

bool NotTheSame;

AgainstList = new TStringList;

Files = new TStringList;

do

{

Synchronize(SetFile);

Synchronize(SetAgin);

}while(!Terminated);

if (one)

{

fclose(one);

}

if (two)

{

fclose(two);

}

FreeOnTerminate = true;

Terminate();

}

//---------------------------------------------------------------------------

Simple eh ? The actual declarations here, Loop1, Loop2, one, two and NotTheSame we’ll use later when we put in our actual search algorithm. For now, we allocate memory for our TStringLists, and enter a do/while loop, which effectively stops our thread from terminating unless we let it.

Before going further, note the following. At the end of the Execute function are four lines. The first two ifs check to see if the file pointers actually point to something, and if they do, close the files they point to. Next comes FreeOnTerminate, which we set to true. This means that any memory allocated by the thread object is freed, and the thread object itself is freed when it terminates, if we didn’t do this, we would have to free resources ourselves in the main application. This is important because of the way we close threads ourselves. If we left it till our application closed, we could end up using a lot of unrequired resource.

Next in our list are the synchronised function calls, SetFile and SetAgin. Lets add these to our JSearch.cpp file before we explain their simplicity, (yes you have to type it all:)

//---------------------------------------------------------------------------

void __fastcall JSearch::SetFile()

{

CheckMask = Form1->fil1->Text;

Files->Text = Form1->fl1->Items->Text;

CheckDirec = ExtractFilePath(Form1->fl1->FileName);

}

//---------------------------------------------------------------------------

void __fastcall JSearch::SetAgin()

{

AginMask = Form1->fil2->Text;

AgainstList->Text = Form1->fl2->Items->Text; AginDirec = ExtractFilePath(Form1->fl2->FileName);

}

//---------------------------------------------------------------------------

What we have here are two functions which set appropriate variables within our thread. The first sets our first file list, directory and mask, the second our second list, directory and mask. They could be combined into one function, but I chose to separate them.

(c) Our loops:)

Our thread will work through two loops whilst conducting the comparisons. It will in effect scroll through list one, and for each item in list one, compare it to each item of list two. Enter the following code into our Execute() function, (trust me, this function is going to be large:)

//---------------------------------------------------------------------------

void __fastcall JSearch::Execute()

{

//---- Place thread code here ----

int Loop1, Loop2; //, Loop2Start;

FILE *one, *two;

bool NotTheSame;

AgainstList = new TStringList;

Files = new TStringList;

do

{

Synchronize(SetFile);

Synchronize(SetAgin);

Loop1=0;

while((Loop1 <= Files->Count-1) && (!Terminated))

{

Name1 = CheckDirec + Files->Strings[Loop1];

if((CheckDirec == AginDirec) && (CheckMask == AginMask)

{

Loop2 = Loop1;

}

else

{

Loop2 = 0;

}

while(((Loop2 != AgainstList->Count) && (!Terminated)))

{

Name2 = AginDirec + AgainstList->Strings[Loop2];

Loop2++;

}//end of loop2

Loop1++;

}//end of loop1

//finished so may as well terminate

Terminate();

}while(!Terminated);

if(one)

{

fclose(one);

}

if(two)

{

fclose(two);

}

FreeOnTerminate = true;

Terminate();

}

//---------------------------------------------------------------------------

The loops are very important to us as they control the flow of our comparisons. I’ve used while loops to enable us to check more than one thing at a time, and the only thing to note is that the counts are -1 each time. If you remember, a loop laid out in this fashion will always run once, so in our case, on entry, if the number of items in our list is 0, we check Loop1 (0) against Files->Count(0) -1, i.e. less than Loop1 so it doesn’t enter the loop. If our count is 1, Loop1(0) Files->Count(1)-1, i.e. Loop1 is equal to our count so it runs, dropping when Loop1 = 1 at the end. The next thing that must be true for our while loop to run is the !Terminated statement. If you’ve read the threads tutorial, you’ll know that Terminated is a boolean value which all threads have. In our case, if Terminated is true, it means that our user or application has requested that the thread be terminated, so we terminate, (albeit at the end of the loop).

We’ll use terminated throughout this function to see if we should be terminating the thread, and please note this usage, it is possible for your thread to be â€Ĺ›locked” into doing something, and whilst killing the application kills the thread, a little thought on your part means that your thread should terminate when told !

So to recap,

Loop1=0;

while((Loop1 <= Files->Count-1) && (!Terminated))

{

The next section set’s up the filename Name1, by adding the item at Loop1 to our directory. We then do something to speed up the whole process, by saying that if both the search Directory and Mask are the same as the against Directory and Mask, we don’t need to check every file twice ! This is done by setting the start of Loop2 = to the start of Loop1, otherwise, we set Loop2 to 0.

(If this logic isn’t obvious to you, think in these terms. A directory of two files, file1, file2, compared against itself. First run through, Loop1 = 0, file1, compared to Loop2 = 0, file1, (we remove this later:). Next run, Loop1=0, file1, compared to Loop2 =1, file2. Next, Loop1=1, file2 compared to Loop2=0, file1, but we’ve just done that ! So we don’t bother to do it again).

Name1 = CheckDirec + Files->Strings[Loop1];

if((CheckDirec == AginDirec) && (CheckMask == AginMask))



{

Loop2 = Loop1;



}

else

{

Loop2 = 0;



}

Now we enter our second while loop, in the same manner as the first. Again the important note here is that we check Terminated, to free our thread as quickly as we can, we get the second filename, increment our loops, and then break our overall do/while loop, with the call to Terminate(). At this point notice something, we control our thread. We control what is being done when we want it to be done, and the only other things which can access our thread are Windows and our parent application.

while(((Loop2 != AgainstList->Count) && (!Terminated)))

{

Name2 = AginDirec + AgainstList->Strings[Loop2];

Loop2++;

}//end of loop2

Loop1++;

}//end of loop1

//finished so may as well terminate

Terminate();

Note this, our application is a thread as far as Windows is concerned, and our thread is a thread of our application. When Windows terminates, it asks all it’s threads to terminate, and when it asks our application to terminate all it’s threads before terminating itself. Ensure you understand this. When Windows is given an Exception from our thread, it ignores it, the fault lies with something it doesn’t wish to know about, it is only concerned with â€Ĺ›it’s own” threads, our application. As I mentioned earlier whilst developing this thread, I kept throwing exceptions all over the place. These were handed to my application, which said Oh, I’ll throw a message box up, (all CBuilder programs handle these by default), but the application doesn’t terminate itself until the user accepts the messagebox, thus, bring the main app window to the front and fire another thread, get another exception and so on. I stopped at ten exceptions, but it does show the control flow between your thread, application, (CBuilder if you’re running from within the IDE) and Windows. GPF’s however are another matter:)

(d) Our Search â€Ĺ›Engine”

Now we’ve finished with Loop1, we just need to put the working bit inside Loop2. Enjoy yourself and type the following complete into between the two lines shown in the Execute() function.

{

Name2 = AginDirec + AgainstList->Strings[Loop2];

if((one = fopen(Name1.c_str(), "rb")) == NULL)

{//error, should terminate

Synchronize(Error);

Terminate();

}

if((two = fopen(Name2.c_str(), "rb")) == NULL)

{//error, should terminate

Synchronize(Error);

Terminate();

}

NotTheSame = false;

if(!Terminated)

{

Synchronize(UpdateCounter);

Synchronize(UpdateLog);

while(!feof(one) && (!Terminated))

{

//do comparisons

if(Name1 == Name2)

{

NotTheSame = true; //don't bother

}

if (fgetc(one) != fgetc(two))

{//flag not the same

NotTheSame = true;

}

if(NotTheSame || feof(two) )

{//fseek to endof one



lseek(fileno(one), 0L, SEEK_END);

}

}

if(!NotTheSame)

{

if(!Terminated)

{

Synchronize(UpdateDisplay);

}

}

fclose(one);

fclose(two);

}

Loop2++;

}//end of loop2

Okay, there’s a lot there. The first few lines deal with opening the files we wish to compare.

They’re both wrapped in an if statement, in other words if they return a null pointer, we call the function Error, which simply places a message in our debug window, and then puts in a call to the threads Terminated flag with the Terminate() function. We then set the NotTheSame flag to false for later.

{

Name2 = AginDirec + AgainstList->Strings[Loop2];

if((one = fopen(Name1.c_str(), "rb")) == NULL)

{//error, should terminate

Synchronize(Error);

Terminate();

}

if((two = fopen(Name2.c_str(), "rb")) == NULL)

{//error, should terminate

Synchronize(Error);

Terminate();

}

NotTheSame = false;

The next line uses the Terminated flag to see if we bother entering the next stage. If you study the process of this loop throughout, you’ll notice that if Terminated is true at any point, the

program control will fall out of all loops, and that we check Terminated as frequently as makes sense, and certainly before entering any time consuming processes, (or even during them.

if(!Terminated)

{

Synchronize(UpdateCounter);

Update the counter function

Synchronize(UpdateLog);

Update the Debug window

Next we start to compare the files. We do this by checking two flags, Terminated again, and the end of file1. We then check to see if the file names are identical, (right down to the path), and fall out of our loop fairly quickly if this is the case. This should really be done before entering the loop, but for simplicity and clarity in the use of the Terminated flag, I’ve placed it here so as not to introduce another flag. We then read a character from file one and compare it to a character from file two. If they’re not the same we flag it as true, and then end the sequence if the files are either the same file, or contain a character which is not the same, or when we reach the end of file2. The statement if(NotTheSame || feof(two) ) checks this, and then sets the file pointer for file one to the end of fileone, so that the while loop ends, (remember, at the bottom of the while loop Terminated is also checked).

while(!feof(one) && (!Terminated))



{//do comparisons

if(Name1 == Name2)

{//don't bother

NotTheSame = true;

}

if(fgetc(one) != fgetc(two))



{//flag not the same

NotTheSame = true;

}

if(NotTheSame || feof(two) )

{//fseek to endof one



lseek(fileno(one), 0L, SEEK_END);

}

}

Okay, the last part is quite straightforward. If the above while has finished, we check the status of NotTheSame, and only if it’s true do we Update our display. Again, we put a final check on Terminated here before calling the Update function. This is because the process of calling the UpdateDisplay, which updates the top RichEdit on Form2, could be delayed by many things, other threads, or even our user saving the contents of the RichEdit, so we only want to update it if the thread should still be running. Close the two files and increment Loop2 or drop from the loop.

if(!NotTheSame)

{

if(!Terminated)

{

Synchronize(UpdateDisplay);

}

}

fclose(one);

fclose(two);

}

Loop2++;

}//end of loop2

There is a lot there, so check through it and make sure you understand what’s happening, and more importantly why. Track the Terminated flag and see how our thread can terminate at frequent intervals. This is the reason that While loops are used, it’s harder to break for loops.

(e) Our final Bit,

Here are the remaining functions, all concerned with updating the output. Again you have to type all the code in full:(

//---------------------------------------------------------------------------

void __fastcall JSearch::UpdateDisplay()

{

//for now lets update the richedit with our duplicate files Form2->r1->Lines->Add(Name1 + " is identical to " + Name2); Form2->Update();





}

Straightforward update of the display, adding a line to the top RichEdit of Form2.

//---------------------------------------------------------------------------

void __fastcall JSearch::Error()

{//display on error

Form2->r1->Lines->Add("File Open Error");

}

//---------------------------------------------------------------------------

This gives us a message that an error occurred, and as the only place we called it from is the fileopening parts, that is the most likely cause.

//---------------------------------------------------------------------------

void __fastcall JSearch::UpdateCounter()

{

//update the counter for number of files.

Form1->Counter++;

Form1->Panel2->Caption = Form1->Counter;

Form1->Update();

}

The above function increments the counter, for all threads. This means that if we’ve more than one thread running the counter can race up:)

//---------------------------------------------------------------------------

void __fastcall JSearch::UpdateLog()

{

Form2->r2->Lines->Insert(0, "Comparing " + Name1 + " to " + Name2);

}

//---------------------------------------------------------------------------

And finally, this message continually updates the log window by telling us which files are being operated on.

Compile and run the application, and just hit Go with the defaults. You should find every file in the directory of your project being checked, and some things should be identical, (*.~cp *.cpp for instance.). Play with it as you wish:)

No liability is accepted by the author(s) for anything which may occur whilst following

this tutorial







Wyszukiwarka

Podobne podstrony:
THREADS id 2053270 Nieznany
CISAX01GBD id 2064757 Nieznany
SGH 2200 id 2230801 Nieznany
111003105109 stress id 2048457 Nieznany
CIXS201GBD id 2064760 Nieznany
TOCEL96GBB id 2491297 Nieznany
1078 2 FEA209544 128UEN A id 22 Nieznany
McRib(r) Sandwich id 2201097 Nieznany
BD V600 L3 C A3 V1[1] 1 id 2157 Nieznany
DOC0534 id 2032985 Nieznany
8 17 id 2009842 Nieznany
REKAN02GBBT id 2491218 Nieznany
cialo albatros id 2035175 Nieznany
[17] FR540NT010 id 2085454 Nieznany
RO7503GBDT id 2491245 Nieznany
VOLUP98GBD id 2134841 Nieznany
cienie w raju rebis id 2036016 Nieznany

więcej podobnych podstron