6

I've got a almost completed app now and the next feature I want to implement is threading. I chose to go with BeginThread(), although am aware of TThread in delphi. The problem I'm coming across is the structure of BeginThread() call. Normally the line in the program that would call the function I want to be threaded is

CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);

op is a integer.

The line I've switched it out for to create a thread from it is

BeginThread(nil,0,CompareFiles,Addr('form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op'),0,x);

From the little amount of infromation I can find on how to actually use BeginThread() this should be a fine call, however on compling all I get is complier errors regarding the structure of my BeginThread() statement paramenters.

EDIT FOR INFORMATION.

The current procedure that calls CompareFiles is

procedure TForm1.Panel29Click(Sender: TObject);
var
op,x : integer;

begin
    if (Form1.Edit3.Text <> '') AND (Form1.Edit4.Text <> '') then
        begin
          op := 3;
          if RadioButton7.Checked = True then op := 0;
          if RadioButton3.Checked = True then op := 1;
          if RadioButton4.Checked = True then op := 2;
          if RadioButton5.Checked = True then op := 3;
          if RadioButton6.Checked = True then op := 4;
          CompareFiles(form1.Edit3.Text,Form1.Edit4.Text,Form1.StringGrid2,op);
        end;
end;

If I was to use TThread as suggested by a couple of people, and as displayed by Rob below, I'm confused at how a) I would pass op,Edit3/4.Text and StringGrid2 to the CompareFiles. Guessing from the example of TThread I've seen I thought I would replace the code above with TCompareFilesThread.Executeand the put the current code from Panel29Click into TCompareFilesThread.Create and then add

FEdit3Text := Edit3Text;
FEdit4Text := Edit4Text;
FGrid := Grid;

to this

FEdit3Text := Form1.Edit3.Text;
FEdit4Text := Form1.Edit4.Text;
FGrid := Form1.StringGrid2;

But I've got this nagging feeling that is totally off the mark.

Flatlyn
  • 2,040
  • 6
  • 40
  • 69
  • "Complaints about things not matching" is not a very useful description. Could you edit your question and paste a copy of the error message in please? – Mason Wheeler Jan 25 '11 at 01:23
  • 2
    I'm also curious why you're choosing to *not* use TThread? Using global thread functions without a nice object encapsulation vastly complicates things related to concurrency and race conditions. I'm also curious why you are nearing the completion of the application and then decided to add threading. IMO, threading should be considered from the outset and not an afterthought. There is a high probability of threading land-mines if not taken into account early enough. – Allen Bauer Jan 25 '11 at 01:28
  • The reason threading is getting implemented now is that this is a learning app for me. Threading was always planned just only getting round to implementing it now that the apps core features are complete (mostly). As for TThread, I can can't seem to find an good explanation on how to use it. – Flatlyn Jan 25 '11 at 04:20
  • 2
    I started learning about Threading with Delphi here: http://www.eonclash.com/Tutorials/Multithreading/MartinHarvey1.1/ToC.html Maybe a good start for you too? – Bulan Jan 25 '11 at 09:19
  • Do *not* call the `Execute` method yourself. The thread will call that automatically. If you call it yourself, you'd be calling it from the main thread, at which point you lose all advantage of running in a separate thread. Please consider giving your forms and controls **meaningful names**. Your computer chose the names you have now, and they're terrible. You're a human, and you know better, so exert your control and name them according to what they do instead of according to the order you happened to put them on the form. – Rob Kennedy Jan 25 '11 at 15:25

1 Answers1

14

That's not at all the way to use BeginThread. That function expects a pointer to a function that takes one parameter, but the function you're trying to call wants four. The one parameter you're giving to BeginThread for it to forward to the thread procedure is a string, but you evidently hope that some sort of magic will turn that string of characters into the values that those variables contain.

That's not how Delphi works, and even for the languages that can do something like that, it's generally discouraged to actually do it.

To pass multiple parameters to BeginThread, define a record with all the values you'll need, and also define a record pointer:

type
  PCompareFilesParams = ^TCompareFilesParams;
  TCompareFilesParams = record
    Edit3Text,
    Edit4Text: string;
    Grid: TStringGrid;
    Op: Integer;
  end;

Change CompareFiles to accept a pointer to that record:

function CompareFiles(Params: PCompareFilesParams): Integer;

To start the thread, you'll need to allocate an instance of that record and populate its fields:

var
  Params: PCompareFilesParams;
begin
  New(Params);
  Params.Edit3Text := Edit3.Text;
  Params.Edit4Text := Edit4.Text;
  Params.Grid := StringGrid2;
  Params.Op := op;
  BeginThread(nil, 0, @CompareFiles, Params, 0, x);

Implement CompareFiles like this so that the record will get freed before the thread terminates:

function CompareFiles(Params: PCompareFilesParams): Integer;
begin
  try
    // <Normal implementation goes here.>
  finally
    Dispose(Params);
  end;
end;

You can make it all a lot easier if you just use TThread, though. You can make your descendant class have as many parameters as you want in its constructor, so you don't have to mess around with dynamically allocating and freeing a special record.

type
  TCompareFilesThread = class(TThread)
  private
    FEdit3Text,
    FEdit4Text: string;
    FGrid: TStringGrid;
    FOp: Integer;
    procedure Execute; override;
  public
    constructor Create(const Edit3Text, Edit4Text: string; Grid: TStringGrid; Op: Integer);
    property ReturnValue;
  end;

constructor TCompareFilesThread.Create;
begin
  inherited Create(False);
  FEdit3Text := Edit3Text;
  FEdit4Text := Edit4Text;
  FGrid := Grid;
  FOp := Op;
end;

procedure TCompareFilesThread.Execute;
begin
  ReturnValue := CompareFiles(FEdit3Text, FEdit4Text, FGrid, FOp);
end;

Instead of calling BeginThread, you just instantiate the class and let it run:

var
  ThreadRef: TThread;


ThreadRef := TCompareFilesThread.Create(Edit3.Text, Edit4.Text, StringGrid2, Op);

There's more to using threads, such as knowing when the thread has finished running, but I think you have enough to get started. One last thing to beware of, though, is that TStringGrid is a VCL control. You mustn't do anything with it from this new thread you create (regardless of how you end up creating it). Eveything you do with the grid control need to be done from the main thread. Use TThread.Synchronize and TThread.Queue to shift any VCL operations onto the main thread. Your file-comparing thread will wait for the synchronized operation to complete, but it will keep running without waiting for a queued operation to complete.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • +1 for the great explanation! but "Delete(Params);" should be "Dispose(Params);" – arthurprs Jan 25 '11 at 02:34
  • @Arthurprs Oops! Can you tell I use C++ all day? – Rob Kennedy Jan 25 '11 at 02:45
  • If I can figure out how to use TThread then I will. For example with the sample code you have there, what/how would I call it in the OnClick Procedure , which sets op and decides when to run the CompareFiles. – Flatlyn Jan 25 '11 at 04:22
  • Updated original post with more information regarding previous comment. – Flatlyn Jan 25 '11 at 04:45
  • @TheFlatline: `TThread` is very easy to use and there are plenty of tutorials out there on how to do it. Just subclass from `TThread`, create a constructor that takes all you need in the thread and override the protected `Execute` method. – jpfollenius Jan 25 '11 at 10:01