Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

TComponent and TThread 2

Status
Not open for further replies.

Glenn9999

Programmer
Jun 19, 2004
2,312
US
I'm working on writing a non-visual VCL component, but I'm having a bit of a difficulty getting it working completely right.

I have the TComponent creating a TThread to do the function of the TComponent, and firing some events that the main program can define.

Now I seem to have the events doing okay (albeit slower than my non-thread test version), by encapsulating that call in a procedure and then using Synchronize on that procedure. Is there a better way to handle that?

Then the big functionality problem: The functionality of the events requires that certain static information obtained at the beginning of the thread's execution (in PUBLIC variables) be made available in the TComponent. I'm trying to copy the variables over after the thread is started (with and without delay), but I'm not getting it returned. Any thoughts on how to solve this?

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Instead of Synchronise, you could try setting up some message methods in the TComponent. These will then fire in the main thread when it's idle. This is a slow way of doing it though.

For the functionality problem:

Add some public properties to your TThread, and some associated Read methods. Have a private thread Flag variable set to false (by default), set it to True once the property values are available. In the property read methods, use a repeat/sleep/until Flag=True loop to delay reading the property values.

That's one way that should work. I'm sure a more elegant solution exists.
 
I suppose that the thread in your component is doing some blocking operations (that would be a reason using a thread).

Define an object with all thread related variables and add it as a private variable of your TComponent. When you create the thread, inject the object (use a custom constructor). Guard it with a synchronisation object (like a critical section or a mutex if you go IPC).

concerning the events:

make an invisible messagewindow (AllocatHwnd) and make your thread aware about it. Whenever the thread wants to activate an event, use PostMessage to the message window.
Your WndProc (running in the context of the main vcl thread) must dispatch the message and from there you call the event handler on the component.

the wndproc can be very simple:

Code:
procedure TksPacketTrigger.WndProc(var Msg: TMessage);
begin
 if Msg.Msg > WM_USER then
  Dispatch(Msg)
 else
  DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;

on your component define any user defined messages you need:

Code:
TksPacketTrigger = class(TObject)
   private
   ....
   protected
    { Protected declarations }
    // message handlers
    procedure wmProcessData(var Msg : TMessage); message WM_USER_PORTDATA;
    ...
    procedure WndProc(var Msg: TMessage);

in the message procedure (wmProcessData in this case)
you can do things like
Code:
procedure wmProcessData(var Msg : TMessage);
begin
 if Assigned(FOnData) then 
  FOnData(your parameters...)
end

Please don't use repeat until with a flag (which is a no no)
there are plenty of synchro objects out there. In that case you can use an event (lookup CreateEvent) and use WaitForSingleObject API.

There is a good article about threading here:


I suggest people new to threading to read the whole thing through...

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Thanks for the suggestions. It looks like the boolean flag and loop works for what I need to get this part working, assuming there isn't a bug I'm not seeing yet. And yes, I'm sure there is probably a more elegant solution I'm not aware of, but that can come after I finish and test the component fully and I can figure out how to simplify things.

I figured on using the message dispatching as well once I could prove the component workable.



It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 

please don't use the loop!
Don't mix threads with unprotected global variables

it is as simple as:

Code:
type 

 TYourThread = class(TThread)
 private
   FOwner : TComponent; // reference to owner 
 protected
   procedure Execute; override;
 public
   Event : THandle;
   constructor Create(AOwner : TComponent);
   destructor Destroy; override;
 end;  

implementation

constructor TYourThread.Create(AOwner : TComponent);
begin
 inherited Create(True);
 FEvent := CreateEvent(nil, True, False, nil);
 FOwner := AOwner;
 Resume; // starts our thread
end;

destructor TYourThread.Destroy;
begin
 CloseHandle(FEvent);
end;

procedure TYourThread.Execute;
begin 
 DoSomeWork;
 SetEvent(FEvent); // signal that we are done
end;

in your component code you can now do this:
Code:
 ...
 YourThread := TYourThread.Create(Self);
 if WaitForSingleObject(YourThread.Event, 0) = WAIT_OBJECT_0 then
 begin
  this part will be executed AFTER thread has called SetEvent()
 end;

/Daddy



-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Thanks again, that works much better! I find a lot of the problem is more picking up the practical things to do with threads more than any basic concepts (I've done and posted simple thread code here). Unfortunately, it seems you need to do it to do anything long, so they need done and done well.

My problem actually is more with debugging them and designing them. Case in point is the thing I'm working on right now. I notice in my testing that the thread hangs and doesn't terminate, but performs its job successfully. This makes use of WaitForSingleObject lock up the program as well. The problem with debugging this is I really don't know how, since all the tricks I know don't work right or mess the thread up.

Then on design, I don't know what else is really thread-safe (I'm using WinInet on this current case if that helps in that determination), or how to prove something works. Then I really didn't pick up in that document what your thread needs to encompass. Like with this one, the initialization code really could be outside of the thread logically, but I tried that and it didn't work.

Some good way to learn the practical considerations of threads would be useful.



It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
assume that nothing is threadsafe, unless explictly stated by the API documentation.
As for your thread problem, show some code. Threads need to be programmed carefully as they tend to introduce subtle issues when not done right...

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Threads need to be programmed carefully as they tend to introduce subtle issues when not done right...

Indeed. I found out that using Synchronize caused my thread termination problem. However, I run into the other problem I have had with these things, debugging message problems.

TComponent:
Code:
protected
  procedure MsgHandler(var Msg: TMessage);  // wndproc

FHandle := AllocateHWnd(MsgHandler);
FDownloader.WinHandle := FHandle;
...
(thread call and WaitForSingleObject(thread)
...
DeallocateHWnd(FHandle);

TThread (in many places):
Code:
PostMessage(Self.WinHandle, WM_MyMessage, 0, 0);

the procedure MsgHandler fires twice when DeallocateHWnd is called. Also, wouldn't you have to pass a pointer in PostMessage to get any information out bigger than a DWord? Wouldn't that make this not thread-safe?

How do you debug these things?

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
I found out that using Synchronize caused my thread termination problem
Sounds like a simple deadlock.
do you run the thread with FreeOnTerminate = True??

Also, wouldn't you have to pass a pointer in PostMessage to get any information out bigger than a DWord? Wouldn't that make this not thread-safe?

It Depends. If the thread creates an object and fills it with data, calls postmessage and after that the thread no longer touches the object, *then* it is thread safe. Only catch is that the consumer thread is responsible for freeing the object. If the thread sends an object that it still used by the thread, you must guard the object with a critical section, ensuring that only one thread can manipulate the object at a certain point in time...

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Sounds like a simple deadlock.
do you run the thread with FreeOnTerminate = True??

Yes.

It Depends. If the thread creates an object and fills it with data, calls postmessage and after that the thread no longer touches the object, *then* it is thread safe. Only catch is that the consumer thread is responsible for freeing the object. If the thread sends an object that it still used by the thread, you must guard the object with a critical section, ensuring that only one thread can manipulate the object at a certain point in time...

For what I'm working on (a file downloader module for a project I've been working on), any information would be exclusive to the thread in question (one TComponent, one TThread at the moment, I don't think this will change since the TComponent is a VCL wrapper for the TThread). So there shouldn't be any issues with concurrent threads and passing out event information, unless the issue was made by the programmer. Anyhow, the events:

Event #1: After initialization, an event fires to allow change of download location and file name. Part of the initialization code obtains the filename on the server. If the event is defined to have a TSaveDialog, it will look like your average web browser does when you try to download something.

Event #2: Every second, a status event gets fired. This contains a QWord indicating the number of bytes sent. Coupled with the download size, this gives the event defined a chance to do a number of things with the user interface in VCL.

I'll probably try to work on the PostMessage parts a little more, but anyone have any ideas why the WndProc I define in AllocateHWnd within the TComponent doesn't run except when it's deallocated?

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
I'll probably try to work on the PostMessage parts a little more, but anyone have any ideas why the WndProc I define in AllocateHWnd within the TComponent doesn't run except when it's deallocated?

I did a scaled down version of what I was trying and figured this out. Turns out AllocateHWnd doesn't play well in certain circumstances, and started working when I moved it to the Create section.

If I can figure out how to pass the data successfully, I should have it done soon and can share it if people here want it.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
The thing I talked about here actually back-ends into another question of mine since I generated in essence another example involving the question with respect to threading.

If I have an asynchronous function, which is what was created here, what is the best practice in terms of locking the form and allowing the process to complete without any interruptions?

I have a flag (IsCompleted) along with a OnCompleted event (to Delphize? the question). One can obviously do a repeat/a.p/sleep/until IsCompleted loop. But I'm thinking it might seem better to make the last thing in a button event (or other command event) the asynchronous function, lock out all the controls and then unlock the controls (and perform any status changes) in the OnCompleted event. Though doing that would still make the form pretty vulnerable to being interrupted.

Any ideas on this one?

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
you have 2 contradictions here:

"asynchronous" and "locking the form".

The whole point of threading is to free up the main vcl thread (and thus the GUI).
You need to think about the way you are interacting with the gui when you go async. Here's some food for thought about UI design:
To answer your question, I would show a modal splashscreen (with a progressbar if needed, but that means you need some interaction with the thread) before you call the function and hide it when completed, no need to lock controls...

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
The whole point of threading is to free up the main vcl thread (and thus the GUI).

So how do you solve the problem when you have a TButton which invokes an asynchronous process that the user can click multiple times? This is what I'm talking about. The GUI needs to be responsive, yet prompt the user to "wait until I'm done with your request, you can pause or cancel if you want while I'm running the request, but you can't run another until I'm done with this one".

And yes I know you can rig multiple requests, but let's say for the sake of the GUI design and function you can't.

Code:
AsyncButton.Enabled := false;
DoAsyncProcess;
????
AsyncButton.Enabled := true;


It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
in that case disabling the button would to the trick.
In case of multiple controls, group them in a panel and disable the panel...

caller:

Code:
AsyncButton.Enabled := false;
DoAsyncProcess;

OnComplete event:
AsyncButton.Enabled := true;

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Thanks for all your help!

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top