Solved

multi-threading and dynamic objects

Posted on 2004-08-02
14
1,055 Views
Last Modified: 2008-03-06
First off, I have no problem implementing threads into my apps.
But I do have troubles when I try to incorporate dynamic object arrays inside those threads.
I have been struggling with it for a long time. And I cant seem to be able to find any info on it.

This is what Im tryin to do:
I have a http component download a web page using thread so I can parse the html.
I want to run several threads of downloading of the web page at once.
Which can be about 6-7 threads right now, but might be more in the future.

I have used several different types of threads, some made myself and some others like the TBMThread set @ http://www.mitov.com/Dwnd/dwnd.html

{ create a dummy class for IEHTTP Events and the BMDThreads Events }
type
  TEventHandlers = class
    procedure ShowProgress(Sender: TObject) ;
    procedure ThreadOnExecute(Sender: TObject; Thread: TBMDExecuteThread;
      var Data: Pointer) ;
    procedure ThreadOnStart(Sender: TObject; Thread: TBMDExecuteThread;
      var Data: Pointer) ;
    procedure ThreadOnTerminate(Sender: TObject;
      Thread: TBMDExecuteThread; var Data: Pointer) ;
    procedure ThreadOnUpdate(Sender: TObject; Thread: TBMDExecuteThread;
      var Data: Pointer; Percent: Integer) ;
  end;

var
  Form1  : TForm1;
  Thread1: array[1..2] of TBMDThread;
  HTTP1  : array[1..2] of TIEHTTP;
  mStream: array[1..2] of TMemoryStream;
  TEvent : array[1..2] of TEventHandlers;   { Dummy Class for IEHTTP Event }

procedure TForm1.btnStartClick(Sender: TObject);
var
  i: integer;
begin
  for i := 1 to 2 do begin
    //--------------------------------
    Thread1[i] := TBMDThread.Create(nil);
    //--------------------------------
    try
      TEvent[i] := TEventHandlers.Create();
      { create the event methods fo rthis thread               }
      { the code for these events can be found in the web unit }
      Thread1[i].OnExecute :=  TEvent[i].ThreadOnExecute;
      Thread1[i].OnStart     := TEvent[i].ThreadOnStart;
      Thread1[i].OnTerminate  := TEvent[i].ThreadOnTerminate;
      Thread1[i].OnUpdate   := TEvent[i].ThreadOnUpdate;
      //--------------------------------
      Thread1[i].Start;
    finally
      TEvent[i].Free;
      Thread1[i].Free;
    end;
  end;

end;

procedure TEventHandlers.ThreadOnStart(Sender: TObject; Thread: TBMDExecuteThread;
      var Data: Pointer) ;
var
  i: integer;
begin
  { This event is fired whenever a TIEHTTP thread is started }
  i:= Thread.ThreadID;
  HTTP1[i] := TIEHTTP.Create(nil);
  try
    { Send download progress to this procedure }
    HTTP1[i].OnPacketRead := TEvent[i].ShowProgress;
    HTTP1[i].BlockingMode := false;
      case i of
        0: HTTP1[i].get_url('http://www.google.com') ;
        1: HTTP1[i].get_url('http://www.yahoo.com') ;
      end;
      mStream[i]:= HTTP1[i].result_ms;
    finally
      HTTP1[i].Free;

    end;
end;

if I dont use arrays, it works. But i want to use alot of repetitive code and I want this app easily expandable.
basically I want my app to work like a multi-threaded ftp client, where you can connect and download from several differrent servers at one time.
So what is the best way to implement this?
0
Comment
Question by:LMuadDIb
14 Comments
 
LVL 7

Expert Comment

by:sftweng
ID: 11700745
On examining the source code from www.mitov.vom, I don't see where Thread.ThreadID is declared. Are you certain it only takes on values in the range 1..2 in this example?
0
 
LVL 7

Expert Comment

by:sftweng
ID: 11700747
0
 
LVL 7

Expert Comment

by:sftweng
ID: 11700859
Ouch. My brain is a little slow this time of night - range 0..1?
0
 
LVL 4

Author Comment

by:LMuadDIb
ID: 11706199
ahh crap! ffs... I just checked that and threadID is passing a cardinal well over that range...hhmm..
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 11706520
ThreadID is just the ID that Windows provides to a thread, if these threads are inherited from the TThread class.
0
 
LVL 7

Expert Comment

by:sftweng
ID: 11707490
It didn't appear to me that it was inherited, which is why I asked. However, that does appear to be the root of the problem - perhaps a ThreadID/Index lookup is needed. I have done a similar thing in the past, keeping a list of "known threads", e.g. storing the ThreadID in a list and then using the position within the list as an index into other lists or an array.
0
 
LVL 7

Expert Comment

by:sftweng
ID: 11708205
E.g.,

  knownThreadIds     : TObjectList;

...

{ Add the ThreadID to the known list when the thread is created }

...

FUNCTION KnownThreadIdIndex(tid : THandle) : INTEGER;
{ Returns the index of the caller's thread into the list of known thread }
{ identifiers. If the thread is unknown (e.g., because it's the VCL main }
{ thread, a result of -1 is returned else the return value is the zero-based }
{ index into the knownThreadId list. }
BEGIN
  Result := knownThreadIds.IndexOf(s);
END {KnownThreadIdIndex};
0
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 4

Author Comment

by:LMuadDIb
ID: 11711381
sftweng, your KnownThreadIdIndex function, you use IndexOf(s)... uhmm what is 's'? suppose to be 'tid'?

i:= KnownThreadIdIndex(Thread.Handle);

if I do the above I get this error:
Incompatible types: 'TObject' and 'Cardinal'
0
 
LVL 7

Expert Comment

by:sftweng
ID: 11711472
LMuadDIb, sorry for being less than rigorous. "s" was derived from "tid". This was wrapped into a long proprietary piece of my code that I didn't have time to unravel. The essence of it is that given a TThread "handle", probably a Cardinal, or THandle, you can store that value in a list (it doesn't need to be a TObjectList) and search through the list (sequentially if necessary or, as I proposes, using an "IndexOf" function (works with TObject instances in the list) to find its position in the list - this can be your index into the arrays. It's really just a table lookup.
0
 
LVL 7

Expert Comment

by:sftweng
ID: 11711485
E.g., convert ThreadID into a string and store it into a TStringList. Then on lookup, do the same conversion and find the string (or not) in the list. Use it's position in the list as your array index.
0
 
LVL 4

Expert Comment

by:ErikPhilips
ID: 11713556
Just out of curiosity, what prevents you from creating a class inherited from TThread with pivate variables (TIEHTTP, TMemoryStream,  TEventHandlers) each within the tthread itself?  I thought calling global variables from within a new thread was asking for trouble?
0
 
LVL 7

Expert Comment

by:sftweng
ID: 11713670
ErikPhilips, I took it as a given that LMuadDIb was "stuck" with his components from www.mitov.com. My personal preference would be to do as you suggest and I have done so a couple of times in the past. Unfortunately I can't post that code here.

The risk with global variables exists only when they are being modified by more than one thread; read access should be OK in general. Of course, protecting them with mutual exclusion semaphores is the best thing to do. In this context, I think it would be safe to assume that arrays are safe. Any lists, however, might best be handled with TThreadList or descendants (available since at least V6).
0
 
LVL 7

Accepted Solution

by:
sftweng earned 500 total points
ID: 11713782
I guess I'll give away part of the code, trying to strip out stuff that isn't relevant. I create a process construct that contains a thread and its context. Here's how I create the context:

  TCSPContext = CLASS(TObject)
    PROTECTED
    PRIVATE
      fContextThread   : TCSPThread;
      fContextThreadId : INTEGER;
      PROPERTY fThread   : TCSPThread READ fContextThread;
      FUNCTION fStopRequested : BOOLEAN;
    PUBLIC
      CONSTRUCTOR Create;
      PROPERTY CSPThreadId   : INTEGER READ fContextThreadId;
      PROPERTY StopRequested : BOOLEAN READ fStopRequested;
    END {TCSPContext};

CONSTRUCTOR TCSPContext.Create;
VAR
  callerThreadId : INTEGER;
  threadIndex : INTEGER;
BEGIN
  INHERITED Create;
  callerThreadId := GetCurrentThreadId;
  fContextThreadId := callerThreadId;
  knownThreadsLock.EnqShared;
  TRY
    threadIndex := KnownThreadIdIndex(callerThreadId);
    IF threadIndex < 0
    THEN fContextThread := NIL
    ELSE BEGIN
      fContextThread := TCSPThread(knownThreadIds.Objects[threadIndex]);
    END;
  FINALLY
    knownThreadsLock.DeqShared;
  END;
END {TCSPContext.Create};

Here's the list lookup:

FUNCTION KnownThreadIdIndex(tid : THandle) : INTEGER;
{ Returns the index of the caller's thread into the list of known thread }
{ identifiers. If the thread is unknown (e.g., because it's the VCL main }
{ thread, a result of -1 is returned else the return value is the zero-based }
{ index into the knownThreadId list. }
VAR
  s : String;
BEGIN
  s := ThreadIdToStr(tid);
  Result := knownThreadIds.IndexOf(s);
END {KnownThreadIdIndex};

FUNCTION ThreadIdToStr (tid : THandle) : String;
VAR
  s : String;
BEGIN
  s := IntToStr(tid);
  WHILE Length(s) < 10 DO Insert(' ',s,0);
  Result := s;
END;


The "Enq" and "Deq" procedures are just wrappers around mutual exclusion constructs (critical sections or TMultiReadExclusiveWriteSynchronizer).
0
 
LVL 4

Author Comment

by:LMuadDIb
ID: 11718474
sftweng, thanx for all help! I really appreciate it :)

but sense I broke my hand today, I dont think I will be typing much -- at least not for bit :(
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now