?
Solved

multi-threading and dynamic objects

Posted on 2004-08-02
14
Medium Priority
?
1,104 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
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
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
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
 
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 2000 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

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Monitoring a network: how to monitor network services and why? Michael Kulchisky, MCSE, MCSA, MCP, VTSP, VSP, CCSP outlines the philosophy behind service monitoring and why a handshake validation is critical in network monitoring. Software utilized …
Sometimes it takes a new vantage point, apart from our everyday security practices, to truly see our Active Directory (AD) vulnerabilities. We get used to implementing the same techniques and checking the same areas for a breach. This pattern can re…
Suggested Courses

801 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