Board index » cppbuilder » multi-threading

multi-threading


2003-11-10 10:02:54 AM
cppbuilder94
Hi -
I have an app that displays some graphics using the VTK (OpenGL API). The
graphics displayed depend on solution to some big equations (CPU intensive).
I'd like to place the CPU intensive "solve" method in its own thread, so the
application doesn't "freeze" while the big computations proceed. But I'd
also like the display to update as the "solve" method progresses.
How can this be acheived with BCB ?
much thanks
Tim
 
 

Re:multi-threading

Hi,
Have a look to TThread in your help file. You have to create your own
derived from the TThread class. BCB can do it for you via the File/NEW
Dlg Box. There's an example also int the Examples folder of BCB.
It could be also usefull to use TTimer object in some situation to test
or stop the end of one or all open threads. (i.e.: When user want to
exit your app. gracefully example).
Very fun to deal with threads with BCB!
Good luck,
RW
e a écrit:
Quote
Hi -

I have an app that displays some graphics using the VTK (OpenGL API). The
graphics displayed depend on solution to some big equations (CPU intensive).
I'd like to place the CPU intensive "solve" method in its own thread, so the
application doesn't "freeze" while the big computations proceed. But I'd
also like the display to update as the "solve" method progresses.

How can this be acheived with BCB ?

much thanks
Tim


 

Re:multi-threading

"e" < XXXX@XXXXX.COM >wrote in message news:3faef1cf$ XXXX@XXXXX.COM ...
Quote
Hi -

I have an app that displays some graphics using the VTK (OpenGL API). The
graphics displayed depend on solution to some big equations (CPU
intensive).
I'd like to place the CPU intensive "solve" method in its own thread, so
the
application doesn't "freeze" while the big computations proceed. But I'd
also like the display to update as the "solve" method progresses.

How can this be acheived with BCB ?

Yesterday I posted a problem with Bitmap use in a situation similar to what
you are trying achieve. I posted it in Delphi graphics NG, although my
application happened to be written in BCB, because more graphics programming
had been done in Delphi than in BCB, and VCL is the same. Those posts
describe to some degree how we approached multi-threading. The original
bitmap problem is still unresolved, however.
ML
 

{smallsort}

Re:multi-threading

RW-
Thanks for taking a look. I'm using the TThread class to try to help me.
If I use Synchronize() the graphics on the TForm are updated correctly as
the simulation "solve" method makes progress. However, I am prevented from
interacting with the TForm (e.g. I can't grab the title bar with the mouse
and move the window, or click any of the buttons on the TForm). It seems to
behave as if it is a single thread.
If I choose not to use Synchronize(), I am able to move the window about
while the "solve" method is running but the graphics are fouled up, and I
frequently encounter access violations.
Quote
There's an example also int the Examples folder of BCB.
The BCB example for threads with the sorting comparisons you pointed me to
does exactly what I want. I can move the window around while it's updating
the graphics in each of the three panels with no problems or surprises. It
appears to be very robust. The only difference that I can see between the
structure of that example and my code is that I am not painting directly to
the vcl component within the method passed to Synchronize(). The graphics
are updated several classes away using VTK methods. Also I create a
suspended thread, pass some pointers to the thread, and Resume() it.
My thread class is simple, so I included it below. I appreciate any further
insight you can give. It may be a VTK thread-safe issue.
Thanks again -
TH
// FILE: ThreadSolve.h
//--------------------------------------------------------------------------
-
#ifndef ThreadSolveH
#define ThreadSolveH
//--------------------------------------------------------------------------
-
#include <Classes.hpp>
#include "Simulation.h"
#include <ComCtrls.hpp>
//--------------------------------------------------------------------------
-
class ThreadSolve : public TThread
{
private:
protected:
void __fastcall Execute();
Simulation *sim;
TStatusBar *statusBar;
public:
__fastcall ThreadSolve(bool CreateSuspended);
void setSimulation(Simulation *s,TStatusBar *sb);
void __fastcall stepSimulation(void);
};
//--------------------------------------------------------------------------
-
#endif
// FILE: ThreadSolve.cpp
//--------------------------------------------------------------------------
-
#include <vcl.h>
#pragma hdrstop
//--------------------------------------------------------------------------
-
#include "ThreadSolve.h"
#include "Simulation.h"
//--------------------------------------------------------------------------
-
__fastcall ThreadSolve::ThreadSolve(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//--------------------------------------------------------------------------
-
void ThreadSolve::setSimulation(Simulation *s,TStatusBar *sb){
this->sim = s;
this->statusBar = sb;
}
//--------------------------------------------------------------------------
-
void __fastcall ThreadSolve::Execute()
{
Synchronize(this->stepSimulation);
// this->stepSimulation(); <--------- ERRORS
}
//--------------------------------------------------------------------------
-
void __fastcall ThreadSolve::stepSimulation()
{
this->statusBar->SimpleText = AnsiString("Solving...");
this->sim->solve(); // computes new positions and updates graphics
pipeline
this->statusBar->SimpleText = AnsiString("Solving : DONE !!!");
}
//--------------------------------------------------------------------------
-
#pragma package(smart_init)
 

Re:multi-threading

"John Szuhay" < XXXX@XXXXX.COM >wrote in message
Quote
Is it better to create a new thread and free the thread every minute
or create the thread once then use suspend and resume betwen calcs?
Which is more robust on Windows platorforms?
Suspend/Resume. Creating new thread instances can be expensive on system
resources if you are doing it often. If you are using a lot of threads, you
should reuse them when possible.
Gambit
 

Re:multi-threading

John Szuhay wrote:
Quote
Can anyone suggest documentation on buildfing real-time apps, (i.e. tricks
of the trade, tips, do's and dont's, etc). I am using CBuilder 6.
It depends on how 'real time' you mean... I'm not sure you can build proper
real time apps on windows. Do you mean 'close to real time'? If you want
true real time, you might want to look at a real time OS (QNX?)
--
Vesty.
 

Re:multi-threading

I am developing a real-time app ( 1-minute frequency) and I am using multi-threading for the heavy lifting (numerical calcs) so that the GUI works in between the calcs.
Is it better to create a new thread and free the thread every minute or create the thread once then use suspend and resume betwen calcs? Which is more robust on Windows platorforms?
Can anyone suggest documentation on buildfing real-time apps, (i.e. tricks of the trade, tips, do's and dont's, etc). I am using CBuilder 6.
Thanks,
John
 

Re:multi-threading

Adam Versteegen wrote:
Quote
John Szuhay wrote:


>Can anyone suggest documentation on buildfing real-time apps, (i.e. tricks

of the trade, tips, do's and dont's, etc). I am using CBuilder 6.

It depends on how 'real time' you mean... I'm not sure you can build proper
real time apps on windows. Do you mean 'close to real time'? If you want
true real time, you might want to look at a real time OS (QNX?)

Real Time is not necessarily fast. This is a common misconception, but
real time can be every second, every minute or every hour. The concept
of real time is that the time when an event occurs is guaranteed.
Windows can't and won't guarantee any time because of the way it is
programmed. However, you can work around the second mark without too
much problems. If you want guaranteed real time, you need, as you
pointed out, to change OS, to go with QNX, VxWorks or others...
 

Re:multi-threading

Vesty,
Quote
It depends on how 'real time' you mean... I'm not sure you can build proper
real time apps on windows. Do you mean 'close to real time'? If you want
true real time, you might want to look at a real time OS (QNX?)
Thankfully I do not have strict timing issues in my application. It needs to run once a minute, but it needs to keep going 24/7. I am having hellacious problems making the app robust enough to do this...
I have plugged all the memory leaks, and sorted out the thread fragility problems (I think) by using the Suspend/Resume methods, but damn I am now seeing weirdness with fopen/flcose. My app can run for two days then {*word*99} out because of fopen. I am opening, reading and closing the same file every minute (the file is updated from another process every minute) and I am lock-stepping my app with the other process to avoid file collisions, but still no dice.
Is fopen flakey on Windows platforms?
Is the Borland open() function any better? Why did Borland create this?
Are the C++ std library functions (i.e. ofstream(), ifstream()) better?
Any help would be greatly appreciated.
Thanks,
John
 

Re:multi-threading

John W. Szuhay wrote:
Quote
Vesty,


>It depends on how 'real time' you mean... I'm not sure you can build proper
>real time apps on windows. Do you mean 'close to real time'? If you want
>true real time, you might want to look at a real time OS (QNX?)


Thankfully I do not have strict timing issues in my application. It needs to run once a minute, but it needs to keep going 24/7. I am having hellacious problems making the app robust enough to do this...

I have plugged all the memory leaks, and sorted out the thread fragility problems (I think) by using the Suspend/Resume methods, but damn I am now seeing weirdness with fopen/flcose. My app can run for two days then {*word*99} out because of fopen. I am opening, reading and closing the same file every minute (the file is updated from another process every minute) and I am lock-stepping my app with the other process to avoid file collisions, but still no dice.
Suspend/Resume are a source of problems because of the way they are
implemented in the system.
You should never rely on them but rather use a Mutex (or Semaphore) to
block your thread.
The risk with Suspend is that you will stop your thread in the middle of
a memory write and the memory may have disappeared when you resume. You
should only block a thread when it is safe to do so, hence the use of a
Mutex.
 

Re:multi-threading

I am attempting to implement multithreading in an application but am stuck.
Below I have the elements of my code.
1) I've created 2 objects which I attempt to use to create a threaded HTTP
connection with sync'd updates to the main app.
TSyncClass = class
protected
FWorkCount: integer;
FWorkCountMax: integer;
FStringStream: TStringStream;
procedure BeginDownload;
procedure UpdateDownload;
procedure EndDownload;
public
procedure DoSynchronize(AThread: TIDThread; AMethod: TThreadMethod);
end;
TDownloadThread = class(TidThread)
protected
procedure httpworkbegin(ASender: TObject; AWorkMode: TWorkMode;
AWorkCountMax: Integer);
procedure httpwork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount:
Integer);
procedure httpworkend(ASender: TObject; AWorkMode: TWorkMode);
public
frequestURL: string;
fhost: string;
fport: integer;
fstream: TStringStream;
procedure Run; override;
end;
2) The TSyncClass procedures follow
{TSyncClass}
procedure TSyncClass.BeginDownload;
begin
frmDownload.pbarDownload.Min := 0;
frmDownload.pbarDownload.Max := FWorkCountMax;
end;
procedure TSyncClass.UpdateDownload;
begin
frmDownload.pbarDownload.Position := FWorkCount;
end;
procedure TSyncClass.EndDownload;
begin
frmSoundStage.StrStream.CopyFrom(FStringStream,FStringStream.Size);
end;
procedure TSyncClass.DoSynchronize(AThread: TIdThread; AMethod:
TThreadMethod);
begin
AThread.Synchronize(AMethod);
end;
3) The TDownloadThread procedures follow
{TDownloadThread}
procedure TDownloadThread.Run;
begin
with TIdHTTP.Create(nil) do
begin
try
//populate request header
Request.URL := frequestURL;
OnWork := httpwork;
OnWorkBegin := httpworkbegin;
OnWorkEnd := httpworkend;
Connect(fhost,fPort);
while connected do
Get(Request.URL, fstream);
finally
Free;
end;
end;//with
Stop;
end;
procedure TDownloadThread.httpworkbegin(ASender: TObject; AWorkMode:
TWorkMode; AWorkCountMax: Integer);
var
C: TSyncClass;
begin
C := TSyncClass.Create;
try
with C do
begin
FWorkCountMax := AWorkCountMax;
DoSynchronize(self,BeginDownload);
end;
finally
C.Free;
end;//try
end;
procedure TDownloadThread.httpwork(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Integer);
var
C: TSyncClass;
begin
C := TSyncClass.Create;
try
with C do
begin
FWorkCount := AWorkCount;
DoSynchronize(self,UpdateDownload);
end;
finally
C.Free;
end;//try
end;
procedure TDownloadThread.httpworkend(ASender: TObject; AWorkMode:
TWorkMode);
var
C: TSyncClass;
begin
C := TSyncClass.Create;
try
with C do
begin
FStringStream.CopyFrom(TDownloadThread(ASender).fstream,TDownloadThread(ASender).fstream.Size);
DoSynchronize(self, EndDownload);
end;
finally
C.Free;
end;//try
end;
4) Main form code
downloadThread := TDownloadThread.Create;
try
with downloadThread frmMain do
begin
FreeOnTerminate := True;
frequestURL := 'http://' + frmSystemTest.edtServerHost.text + ':'
+
frmSystemTest.edtServerPort.text + '/GetQueue?clid=' +
InttoStr(TProductionInfo(lbProductions.Items.Objects[lbProductions.ItemIndex]).selectedclipid);
fhost := frmSystemTest.edtServerHost.Text;
fport := StrToInt(frmSystemTest.edtServerPort.Text);
Start;
finally
downloadThread.Free;
end;
5) It all seemed pretty straightforward under the assumption that in my
main form (shown above) when I call "Start" that the "Run" procedure
happens. By the way, I got the
structure for this implementation off of the Internet so I don't know how
valid it is. The assumptions I made were that the "Run" procedure would
run, the idHTTP events would subsequently trigger and
I could use those events to (a) pass download status information (b) capture
the download stream to the main form after the "WorkEnd" event. Clearly my
application fails at (b) and having not tried a multithreaded application
before I couldn't detect that the threading actually occured. That is
setting breakpoints didn't seem to work. So, if anyone could give me a
pointer about where I'm going wrong or some little detail I'm missing or
even a better way of doing this I'd appreciate it.
David
 

Re:multi-threading

Err... OK.
First, how often does your app download a web page? If it performs
downloads often, there is little point in continually
creating/terminating/freeing threads and other complex components, (like
TidHTTP), to do the downloads - it's much easier and safer to keep the
threads and components around.
Then there's this:
Start;
finally
downloadThread.Free;
I'm not sure what 'start' does either but, unless it is a synchronous
signaling mechanism, (which I'm pretty sure it is not), there is a real
chance that the download thread will not run at all because you are
freeing it in the very next line. Even if the OS does run the download
thread immediately, (either on another processor or by preempting the
main thread), the first time the download thread blocks, (as it will on
the TidHTTP socket calls), the main thread will run again and the
download thread will be freed. What happens in TidThread.destroy I do
not know, but I'm pretty sure that you do not want to call it before the
download thread has actually done some downloading.
Remy Lebeau, (Indy expert), may well find other issues/problems <g>
Rgds,
Martin
 

Re:multi-threading

Here is the easy way. I try to show the logic so code will look a bit funny.
Have fun
A Pham
TWorkItem = class
public
.......All your variables
procedure AfterDownload;
{
.....Your codes
}
procedure BeforeDownload;
{
.....Your codes
}
procedure Download;
{
.....Your codes
}
destructor Destroy;
{
.....Free stuff that on constructor or manage by this object
inherited Destroy;
}
end;
TWorker = class(TThread)
protected
FEvent: TSimpleEvent;
FWorkList: TList;
FCritialSection: TCriticalSection;
procedure ClearList;
{
while FWorkList.Count>0 do
begin
TObject(FWorkList[FWorkList.Count - 1]).Free;
FWorkList.Delete(FWorkList.Count - 1);
end;
}
procedure Execute; override;
var
WorkItem: TWorkItem ;
HasWork: Boolean;
{
while not Terminated do
begin
if FEvent.WaitFor(INFINITE) = erSignal then
begin
if Terminated then
Break;
FEvent.ResetEvent;
repeat
FCritialSection.Lock;
if FWorkList.Count>0 then
begin
FWorkItem:= TWorkItem(FWorkList[0]);
FWorkList.Delete(0);
HasWork:= True;
end
else
begin
HasWork:= False;
FWorkItem:= nil;
end;
FCritialSection.UnLock;
if FWorkItem <>nil then
begin
//Rember to use Synchronize only if access stuff to
main form, otherwise ignore by calling straight procedure such as
FWorkItem.BeforeDownload
Synchronize(FWorkItem.BeforeDownload);
Synchronize(FWorkItem.Download);
Synchronize(FWorkItem.AfterDownload);
FreeAndNil(FWorkItem);
end;
until Terminated or (not HasWork);
end;
end;
}
public
constructor Create;
{
inherited Create(True);
FEvent:= TSimpleEvent.Create;
FWorkList:= TList.Create;
FCriticalSection:= TCriticalSection.Create;
Resume;
}
destructor Destroy;
{
ClearList;
FreeAndNil(FEvent);
FreeAndNil(FWorkList);
FreeAndNil(FCritialSection);
inherited Destroy;
}
procedure AddWork(AWorkItem: TWorkItem);
{
FCritialSection.Lock;
FWorkList.Add(AWorkItem);
FCritialSection.UnLock;
FEvent.SetEvent;
}
procedure Terminate;
{
inherited Terminate;
FEvent.SetEvent;
}
end;
Main Form
onCreate;
{
Worker := TWorker.Create;
}
onDestroy;
{
Worker.Terminate;
}
onUserClickRequest;
{
WorkItem:= TWorkItem.Create;
....set all variables
Worker.AddWork(WorkItem);
}
"David Fealkoff" < XXXX@XXXXX.COM >wrote in message
Quote
I am attempting to implement multithreading in an application but am
stuck.
Below I have the elements of my code.

1) I've created 2 objects which I attempt to use to create a threaded HTTP
connection with sync'd updates to the main app.

TSyncClass = class
protected
FWorkCount: integer;
FWorkCountMax: integer;
FStringStream: TStringStream;
procedure BeginDownload;
procedure UpdateDownload;
procedure EndDownload;
public
procedure DoSynchronize(AThread: TIDThread; AMethod: TThreadMethod);
end;

TDownloadThread = class(TidThread)
protected
procedure httpworkbegin(ASender: TObject; AWorkMode: TWorkMode;
AWorkCountMax: Integer);
procedure httpwork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount:
Integer);
procedure httpworkend(ASender: TObject; AWorkMode: TWorkMode);
public
frequestURL: string;
fhost: string;
fport: integer;
fstream: TStringStream;
procedure Run; override;
end;

2) The TSyncClass procedures follow

{TSyncClass}
procedure TSyncClass.BeginDownload;
begin
frmDownload.pbarDownload.Min := 0;
frmDownload.pbarDownload.Max := FWorkCountMax;
end;

procedure TSyncClass.UpdateDownload;
begin
frmDownload.pbarDownload.Position := FWorkCount;
end;

procedure TSyncClass.EndDownload;
begin
frmSoundStage.StrStream.CopyFrom(FStringStream,FStringStream.Size);
end;

procedure TSyncClass.DoSynchronize(AThread: TIdThread; AMethod:
TThreadMethod);
begin
AThread.Synchronize(AMethod);
end;

3) The TDownloadThread procedures follow
{TDownloadThread}
procedure TDownloadThread.Run;
begin
with TIdHTTP.Create(nil) do
begin
try
//populate request header
Request.URL := frequestURL;
OnWork := httpwork;
OnWorkBegin := httpworkbegin;
OnWorkEnd := httpworkend;
Connect(fhost,fPort);

while connected do
Get(Request.URL, fstream);

finally
Free;
end;
end;//with
Stop;
end;

procedure TDownloadThread.httpworkbegin(ASender: TObject; AWorkMode:
TWorkMode; AWorkCountMax: Integer);
var
C: TSyncClass;
begin
C := TSyncClass.Create;
try
with C do
begin
FWorkCountMax := AWorkCountMax;
DoSynchronize(self,BeginDownload);
end;
finally
C.Free;
end;//try
end;

procedure TDownloadThread.httpwork(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Integer);
var
C: TSyncClass;
begin
C := TSyncClass.Create;
try
with C do
begin
FWorkCount := AWorkCount;
DoSynchronize(self,UpdateDownload);
end;
finally
C.Free;
end;//try
end;

procedure TDownloadThread.httpworkend(ASender: TObject; AWorkMode:
TWorkMode);
var
C: TSyncClass;
begin
C := TSyncClass.Create;
try
with C do
begin

FStringStream.CopyFrom(TDownloadThread(ASender).fstream,TDownloadThread(ASen
der).fstream.Size);
Quote
DoSynchronize(self, EndDownload);
end;
finally
C.Free;
end;//try
end;

4) Main form code

downloadThread := TDownloadThread.Create;

try
with downloadThread frmMain do
begin
FreeOnTerminate := True;
frequestURL := 'http://' + frmSystemTest.edtServerHost.text +
':'
+
frmSystemTest.edtServerPort.text + '/GetQueue?clid=' +

InttoStr(TProductionInfo(lbProductions.Items.Objects[lbProductions.ItemIndex
]).selectedclipid);
Quote
fhost := frmSystemTest.edtServerHost.Text;
fport := StrToInt(frmSystemTest.edtServerPort.Text);
Start;
finally
downloadThread.Free;
end;

5) It all seemed pretty straightforward under the assumption that in my
main form (shown above) when I call "Start" that the "Run" procedure
happens. By the way, I got the
structure for this implementation off of the Internet so I don't know how
valid it is. The assumptions I made were that the "Run" procedure would
run, the idHTTP events would subsequently trigger and
I could use those events to (a) pass download status information (b)
capture
the download stream to the main form after the "WorkEnd" event. Clearly
my
application fails at (b) and having not tried a multithreaded application
before I couldn't detect that the threading actually occured. That is
setting breakpoints didn't seem to work. So, if anyone could give me a
pointer about where I'm going wrong or some little detail I'm missing or
even a better way of doing this I'd appreciate it.

David


 

Re:multi-threading

- The application only downloads an xml file intermittently. There can be
long periods of time between downloads, followed by a succession of
downloads. I'll have to assess the trade offs of creating and destroying
objects compared to having threads taking time slots and doing nothing.
- 'Start' was taken from the sample code I used as a model. It is a valid
method and as I first step I just tried to model the sample. The theory was
this: 'Start' calls the 'Run' procedure which in turns calls IdHTTP.Get.
The events associated with the download operation synchronize update
information to the main application. I rationalized that if the thread did
start running that since it was blocking that the thread would not 'Stop'
until everything was downloaded.
-In the meantime I found another sample which I'll examine tomorrow, but I
do have one question that I could use a little guidance on. All of the
examples I've seen pass a string back to the main thread. This is passing a
value. If I want to pass the stream back to main thread, which I believe
will be a pointer will that be a problem if the thread is destroyed? Or do
I somehow have recreate the stream in the main thread?
Thanks,
David
"Martin James" < XXXX@XXXXX.COM >wrote in message
Quote
Err... OK.


First, how often does your app download a web page? If it performs
downloads often, there is little point in continually
creating/terminating/freeing threads and other complex components, (like
TidHTTP), to do the downloads - it's much easier and safer to keep the
threads and components around.

Then there's this:

Start;
finally
downloadThread.Free;

I'm not sure what 'start' does either but, unless it is a synchronous
signaling mechanism, (which I'm pretty sure it is not), there is a real
chance that the download thread will not run at all because you are
freeing it in the very next line. Even if the OS does run the download
thread immediately, (either on another processor or by preempting the main
thread), the first time the download thread blocks, (as it will on the
TidHTTP socket calls), the main thread will run again and the download
thread will be freed. What happens in TidThread.destroy I do not know,
but I'm pretty sure that you do not want to call it before the download
thread has actually done some downloading.


Remy Lebeau, (Indy expert), may well find other issues/problems <g>


Rgds,
Martin
 

Re:multi-threading

David Fealkoff wrote:
Quote
- The application only downloads an xml file intermittently. There can be
long periods of time between downloads, followed by a succession of
downloads. I'll have to assess the trade offs of creating and destroying
objects compared to having threads taking time slots and doing nothing.
NO!! Threads that are not running, eg. are waiting on a
Producer-Consumer Queue, (eg. a Windows message queue), for download
work requests, are not given any processor time at all by the OS. No
CPU is wasted doing nothing. Threads that are not in the running state
are just dead code, nothing is executing and 100% of your CPU is
available for the main thread and/or other apps.
Quote
- 'Start' was taken from the sample code I used as a model. It is a valid
method and as I first step I just tried to model the sample. The theory was
this: 'Start' calls the 'Run' procedure which in turns calls IdHTTP.Get.
The events associated with the download operation synchronize update
information to the main application. I rationalized that if the thread did
start running that since it was blocking that the thread would not 'Stop'
until everything was downloaded.
This is possible, maybee probable, but I would not like to do such a
provocative action <g>
Quote
-In the meantime I found another sample which I'll examine tomorrow, but I
do have one question that I could use a little guidance on. All of the
examples I've seen pass a string back to the main thread. This is passing a
value. If I want to pass the stream back to main thread, which I believe
will be a pointer will that be a problem if the thread is destroyed? Or do
I somehow have recreate the stream in the main thread?

Oooh no! I hate copying bulk data!
Post off the whole stream instance and destroy it later in the main
thread. If using threads that stay around and loop around a queue for
work, you can store everything in one 'transaction object' that contains
both the request and results, (or error/exception message), eg, simply:
-----------------------------
TdownloadTransaction=class
FURI:string;
resultStream:TmemoryStream;
errorMess:string;
constructor create(URI:string);
destructor destroy; override;
end;
..
constructor TdownloadTransaction.create(URI:string);
begin
inherited create;
FURI:=URI;
resultStream:=TmemoryStream.create;
end;
..
destructor TdownloadTransaction.destroy;
begin
resultStream.free;
inherited destroy;
end;
-----------------------------
Create one of these, queue it to the downloadthread or pass it in the
thread constructor. The TidHTTP in the thread does the work and you can
then signal the TDT back to the main thread again, eg. with a
synchronized method or PostMessage.
Rgds,
Martin