Icon View Thread

The following is the text of the current message along with any replies.
Messages 1 to 10 of 32 total
Thread Writing a function that queries data in Thread
Thu, May 15 2014 2:21 AMPermanent Link

Adam H.

Hi,

Within my application I'm wanting a trigger to go and do some validation
checks, and display to the user if any warnings they may need to be made
aware of.

The problem I have is that the validations will take some time by the
time they complete. (Mostly running SQL statements, and displaying a
message if certain criteria is or is not met).

To use a basic example, if I was writing a dispatch program, and the
user put in a record to dispatch a quantity of widgets, I would like to
run a SQL that runs a query to ensure that there are enough widgets in
stock, or show how many widgets the client has ordered before, etc but I
don't want the user to be waiting for the procedure to finish.

I've had some luck with playing with threads before (although it was a
while ago now and my brain is hurting Smiley - but I really haven't had to
pass any information back or forth. In the past they have just been
executing a fixed procedure more than anything else.

So in this case I'm wanting to do something that will:

a) Pass a variable so I can condition the SQL to limit to a particular
stock code.

b) Pass a variable of the amount of stock that is about to be outturned, and

c) If the outturned amount is greater than the stock code - display a
form or dialog saying so.

d) I'm guessing I'm going to need to have some sort of check in place
not to close the parent form until the thread is completed, or a way of
terminating the thread safely if the form is closed. Likewise the popped
up form will need to be closed too if it's created. Does this mean that
the dialog, etc should be a variable within the calling form?


Now with my past experience of threads, I recall:

a) Never use VCL components or visible forms in a thread. In this case,
I guess I can't use the thread to pop up a warning dialog?

b) Is it OK to use synchronize? (I this I recall somewhere it was a good
idea to avoid, but I can't remember).



Is this a feasable idea, and if so what would be the best way to start
going about this? (The above is a basic hypothetical - in my app there's
a bit more to it, but I figure if I can do the above in a test
application, I should be OK).

Thanks to anyone game enough to step into my mess. Smile

Cheers

Adam.
Thu, May 15 2014 5:57 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Adam


Its actually not to difficult if you play by the rules:

1. ANY database components except the engine itself MUST be created within the thread
2. ALL variables used must be local to the thread or be read only, or atomic in size (best to stick to the first part)
3. DO NOT attempt to display any data or messages from within the thread
4. ALL communications with the main thread should be vis PostMessage, wrapped in a critical section or via synchronise (I stick with PostMessage)

Those are the basic rules

What I tend to do is override the threads constructor and pass initialisation parameters in through there eg


TemsParams = record
 ReportBack: HWND; <<<<<<<<<<<<<<<
 LoopTime: integer;
 DoSend: boolean;
 DoReceive: boolean;
 IsolateSessionName: string;
 Archiving: string;
end;

The ReportBack handle in TemsParams is very important since a form's handle can change and if it does any messages you send will get lost. What I do is create a dedicated message handler with its own handle.

constructor TBackgroundEMail.Create(emsParams: TemsParams);
begin
inherited Create(True);
KeepOn := True;
iCrashPoint := 0;
iParams := emsParams;
iSignal := THackSimpleEvent.Create;
iArchiveControl := TStringList.Create;
iArchiveControl.Text := iParams.Archiving;
InitialiseService;
FreeOnTerminate := True;
Resume;
end;

If you're expecting a high volume of these threads to be created (ie loads per second) then you'll want to set up a queue handling system to save the overhead of creating the thread but as you're describing it I wouldn't bother

As far as freeing the thread is concerned then if you stick with PostMessage its not to big a problem - the form won't be there and the message will simply be lost.

Simple numeric data can be sent easily as wParam and lParam. If you want to send text you have to manage the get and free mem youself and pass the pointer in wParam or lParam

In the example you give you could have a record which would have the database path, table name, part code, quantity ordered and pass that into the thread. Pass back the quantity on stock

I do see a possible problem in that if two people place an order simultaneously(ish) then both could be told there's plenty of stock

If you find yoourself struggling then if you want to send me a skeleton app I'll be happy to flesh out the thread part for you.



Roy Lambert
Thu, May 15 2014 6:14 AMPermanent Link

Matthew Jones

It's a complex subject, and my first thought was "I'd not start from here". But as
with travel directions, that's not a useful response. 8-)

For your situation, I'd keep to my simple solution. Create a thread object who's
sole purpose is to do the work you need. Create a class to contain the data it
needs to work, and that you want it to return. Create a global variable and create
an instance of the class for that purpose. Now, when you want the info, set up the
global with the request information, and then create the thread and set it to kill
itself on termination. The thread will mark the data in the class as completed, and
in your form you have a timer to watch for the completed flag, and if so then it
shows the results.

The main thing is to keep the thread completely separate, and communicate only
through an object that is uni-directional. Values that are set up from form to
thread, and others that are used from thread to form. Then you don't have to worry
about concurrency. The completed flag is set only when the thread is done setting
the variables, and then the form knows that it can read them without issue. Don't
use synchronise or anything like that, and you are much safer. Like you close the
form, and the thread is still busy - who cares, it sets the info & flag, dies, and
no one cares if your form never looks at it again.

Only one more thing to worry about is setting many threads going, and you can
handle that various ways. Easiest is to have the thread in a global variable, and
have it set it to nil on termination. Only if the global is nil will you start
another. And put an exception handler around the thread's Execute to make sure that
happens.

Since we are in DBISAM, you need to create a new Session and set it to auto-create
its name.

   g_xThreadSession := TDBISAMSession.Create(nil);
   g_xThreadSession.AutoSessionName := True;
   g_xThreadSession.Open;

   szDatabaseLocation := g_xStartupConfig.DatabasePath;

   m_xWorkQuery := TDBISAMQuery.Create(nil);
   m_xWorkQuery.SessionName := g_xThreadSession.SessionName;
   m_xWorkQuery.DatabaseName := szDatabaseLocation;

   szDatabaseTemp := szDatabaseLocation + '\Temp';
   g_xThreadSession.PrivateDir := szDatabaseTemp;


/Matthew Jones/
Thu, May 15 2014 8:05 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Matthew

>It's a complex subject, and my first thought was "I'd not start from here".

With the example Adam gave that was my first thought as well. To be honest my first thought went along the lines of "what plonker would design a despatch system that didn't have a stock control interface so the quantity on hand was known" but I relented because he did say it was a bit hypothetical.

Thinking about it a bit more if the validation is complex and time consuming threads may make a lot of sense IF several of them can be fired off simultaneously all of which operate independently and the results get aggregated back in the main thread AND, most importantly, the user can actually do something useful whilst this is going on. Otherwise I'm in favour of leaving it in the main thread and showing some distractor - wheels turning, pigeon eating corn or something.

This

<<The main thing is to keep the thread completely separate, and communicate only
through an object that is uni-directional. Values that are set up from form to
thread, and others that are used from thread to form.>>

has me slightly worried. From posts over on the Embarcadero forums it seems that unless the thing being read is read in an atomic fashion (eg boolean) its possible for it to be changing even as you read it. If that's correct then even uni-directional stuff (eg a string) may not be totally safe.

I know you say set a flag to show the thread has finished with the implication don't read until then but I thought it was worth pointing out.

The other option that neither of us mentioned is to write the data into a table - its a database app so you may as well get full value for your investment in DBISAM Smiley

Doing it that way can to totally thread safe and you can poll for the results from the main thread or other places.

Roy Lambert

Thu, May 15 2014 8:22 AMPermanent Link

Matthew Jones

> 4. ALL communications with the main thread should be vis
> PostMessage, wrapped in a critical section or via synchronise

To be clear, that is a list of three, not a PostMessage wrapped in a critical
section. That way madness would lie!

I use critical sections for more complex inter-thread comms when needed in my
"global object".

/Matthew Jones/
Thu, May 15 2014 8:49 AMPermanent Link

Raul

Team Elevate Team Elevate

On 5/15/2014 2:21 AM, Adam H. wrote:
> Is this a feasable idea, and if so what would be the best way to start
> going about this? (The above is a basic hypothetical - in my app there's
> a bit more to it, but I figure if I can do the above in a test
> application, I should be OK).

Adam,

I think you have some great suggestions by Roy and Matthew so i'll  just
add my 2 cents also. It's similar to Roy's in many ways.

1. Assuming you have reasonable amount of these checks you could just
start one whenever needed and let it complete and terminate. You're
essentially creating a new thread each time you need to do this but on
the plus side you don't need to worry about thread comms too much.

2. Create a custom class inheriting from tthread and create a custom
constructor like Roy suggested that accepts all your input parameters.

You should keep track of it in main thread if it could run for a long
time so you could terminate it if app is closed (and thread should check
its terminate flag while running).

3. Create the thread using your custom constructor and that includes all
the input params so no need to worry about passing any data in to the
thread.

4. Thread execute needs to create all the dbisam objects
(session,db+table/queries etc) and use them

5. In terms of notifying your user there are 3 ways to accomplish this
depending on your requirements:

5.1 it's correct that you should not call any VCL dialogs from a thread
but one exception is Windows MessageBox function (with nil owner
window). It is thread safe to be called from thread for simple message
display. I don't generally recommend it but it would mean you don't need
to do any comms back to main thread at all.

5.2 You could use synchronize as it will run on main thread context but
don't block when doing so (i.e. do a nonmodal window or just set some
flags on main thread that then will trigger a message to be displayed
later (using a main timer or some other mechanism)).

5.3 Thread OnTerminate event runs in the context of the main thread as
well so you could this similar to previous point - so if you create an
on-demand thread that runs and then dies this would be ok as well.

5.4 posting message back to main thread handle - best way since it's
nothing shared model and most generic (though requires more setup to
handle message and message handlers etc)

Raul
Thu, May 15 2014 9:08 AMPermanent Link

Matthew Jones

> I know you say set a flag to show the thread has finished with the
> implication don't read until then but I thought it was worth
> pointing out.

Indeed, the key is that you don't read until the writer has said all it ready. A
Bool is atomic enough that this would work. As said in other messages, a critical
section with getter/setters will stop anything more complex getting broken. It also
depends on what you are doing of course - if you are reading the current
temperature every second for display, a mis-read will only be there for a second.
I'd assume the writer is doing the logging long term, so the "bad data" wouldn't be
stored anywhere.

> The other option that neither of us mentioned is to write the data
> into a table - its a database app so you may as well get full value
> for your investment in DBISAM Smiley
>
> Doing it that way can to totally thread safe and you can poll for
> the results from the main thread or other places.

That's where I'd move to, but it would be overkill perhaps. Most of my work
nowadays seem to include a table with a work queue, and threads monitoring for jobs
to do. The WebBuilder shopping system does this for order processing, and it calls
the card process which is a database with work to do and threads to call the card
people. A big step more work though, unless you've done it lots before.

/Matthew Jones/
Thu, May 15 2014 9:10 AMPermanent Link

Roy Lambert

NLH Associates

Team Elevate Team Elevate

Matthew

>To be clear, that is a list of three, not a PostMessage wrapped in a critical
>section. That way madness would lie!

Technically, if I was mad, that would be a list of two -

Number 1 - PostMessage, wrapped in a critical section
Number 2 - synchronise

Any intimation that I am on more than casual acquaintance with reality will be met with a firm denial. After all am I not a politician?

Roy Lambert (written from his nice cosy padded cell)
Thu, May 15 2014 7:01 PMPermanent Link

Adam H.

Hi Roy and Matthew,

Thanks very much for taking the time out to respond and help me.

> With the example Adam gave that was my first thought as well. To be
> honest my first thought went along the lines of "what plonker would
> design a despatch system that didn't have a stock control interface
> so the quantity on hand was known" but I relented because he did say
> it was a bit hypothetical.

I must admit - I got a laugh out of this. (Plus, I haven't heard the
term 'plonker' used in decades <vbg>)

My system isn't really what I described. Since it's so specific to the
particular customers needs, I'd need to go into significant detail to
try and explain what's really going on - so I figure it's easier to
break it down into something that would be relatively common or easy to
understand by most and work on it from a common understanding,
especially because at least for the thread part - what's really going to
be done for the customer is irrelevant. Smile

I must admit though, when I first got half way through your replies, the
first thing that came to my mind was a segment from the IT Crowd:

https://www.youtube.com/watch?v=_eUxpCG2n7o

But thankfully after getting some caffeine, I think I understand the
jist of it now. Smile

I'll go and create a small test based application and see what I can
come up with.

Cheers

Adam.
Thu, May 15 2014 9:32 PMPermanent Link

Adam H.

Hi Raul,

Thanks for your reply. That has certainly helped me to create a sample demo!

Cheers

Adam
Page 1 of 4Next Page »
Jump to Page:  1 2 3 4
Image