Delphi: Separate Thread

EricTaylor
EricTaylor used Ask the Experts™
on
Our software, along with many other business-related actions, retrieves email which it summarizes key elements of in a client data set that the user can interact with in a variety of ways. A shortfall is that the user must still hit a "retrieve mail" button to get the mail since we don't want the main application thread stopped for a few seconds every minute checking to see if there is new mail and then updating the cds if there is any.

This sounds to me like a good candidate for putting the email retrieval in a separate thread, but I've never dealt with threads before, so I'm not sure where to begin or what the pitfalls are. I'd be happy for any foundational guidance on what to do/avoid in setting up the thread correctly, but in particular, would like to know how one handles potential conflicts between what the two threads are doing. e.g., what happens if the email retrieval thread is adding new messages to the cds at the same point that the user is double-clicking some other message in the data grid that is linked to the same cds? Thanks in advance for any help.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
developmentguruPresident

Commented:
I like to wrap potential conflict code in a procedure that, in turn, wraps the code in a critical section.  Both threads can call the procedure which is then thread safe.  If you want to add an item to the list, do it in this manner.  If you want to read an item on the list, do it in this manner as well.  The only side effect is that, while the user is double clicking on a line, the line may change.

  Keep in mind that if you want to update a client dataset that has visual controls tied to it that this will need to be done within a Synchronize call when it is called from any thread other than the main thread.

  Another approach I have used is primarily for loading data in a background thread that can the be used by the main thread.  Each time I create a thread, I add it's name to a list of the running threads.  Then the main application can call a function I create called ThreadIsRunning('MyThreadName') to test if the thread is running, and WaitForThread('MyThreadName') to wait until it is finished.  Since the thread is set to create a TClientDataset that is set to a local variable, well behaved code will not try to use the local variable until the thread is not running.  I further access the local variable via a public function.  This allows me to wrap all of the tests and waiting for the thread to finish inside the method, making it easy for code to be well behaved.  These calls are also using critical sections.

I hope that helps.
Geert GOracle dba
Top Expert 2009

Commented:
have you checked my articles ?
http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/A_6613-Adding-threads-for-loading-data-in-background-to-a-delphi-application.html
http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/A_239-Displaying-progress-in-the-main-form-from-a-thread-in-Delphi.html

in essence you will need 2 sets of data
> 1 cds for the thread
> 1 cds for use by the front end gui

the thread dataset can be a limited portion
i assume you will be retrieving mails and then copying them to the main cds a few at a time
at the time the thread is copying data to the main cds, it will freeze the front-end gui and trigger a data refresh in the end
Sinisa VukSoftware architect
Top Expert 2012

Commented:
You can use OmniThreadLibrary from Primoz Gabrijelcic.

http://otl.17slon.com/
Success in ‘20 With a Profitable Pricing Strategy

Do you wonder if your IT business is truly profitable or if you should raise your prices? Learn how to calculate your overhead burden using our free interactive tool and use it to determine the right price for your IT services. Start calculating Now!

Author

Commented:
sorry for the delay in reply to the various suggestions here... was away for a few days and am now exploring which of these comments seems to apply best to my situation. Thanks for the comments from all.

Author

Commented:
Being a complete novice when it comes to threading, I'm still struggling with parts of this. As I understand this, Geert is suggesting two cds's: one that the main thread uses to display info to the GUI, and one that the background thread writes new emails to.  That part makes sense, but I'm unclear about how to synchronize these two cds's. (Your examples in your articles may have shown this, but this area is so new to me that that I was stumbling in understanding parts of the examples.

DevelopmentGuru: is this where you are suggesting the critical section... at the point where these two data sets are synced? (Sorry, I've also never dealt with a critical section before, so please pardon my ignorance.)

Sinisav: Using an existing library might indeed make this process easier, but at the moment, this is so new to me that I'm not clear what parts I'd have to do and what parts the library does. I suspect this could become useful for other things as well, but I'm just at a loss for a beginning point: I'm not sure which of the many library features I'd need to explore to get the retrieved data into the cds used by the GUI.

Can anyone offer a bit of further guidance?
Oracle dba
Top Expert 2009
Commented:
aha, good questions
why 2 datasets

the gui dataset:
user can search/display (edit ?) the dataset
every change here will trigger a refresh of underlying components (dbgrid/dbedits)
so you will want to limit the number of changes to this dataset

the thread dataset:
used for initial load
and retrieving newer/updated records

adding new records from the thread dataset to the gui dataset
1: lock thread dataset
2: lock gui dataset
3: copy/add records from thread dataset to gui dataset
4: unlock gui dataset > this will trigger refresh of dbgrid
5: unlock thread dataset

1:
lock thread dataset can be done with a CriticalSection
The thread should use the same CriticalSection when adding data to the thread dataset and will wait until the CriticalSection is released by copy process
CritSect.Enter

2: lock gui dataset
This is complex
a: indicate refresh is running
b: Freeze gui > easiest is placing all visible controls on a panel and disabling this panel
c: cds.disablecontrols

3: copy
inserting records in gui dataset and copy contents from thread dataset
> empty thread dataset should be done here too

4: Unlock gui dataset
a: cds.enablecontrols
b: unfreeze gui > unfreeze panel
c: indicate refresh finished

5: Unlock thread dataset
CritSect.Leave
> after this the thread can start processing for new records again

this would be my approach
if you have difficulty understanding threads, that's normal :)
as Primoz says, it's not that programming threads is difficult, it's very very very very difficult
developmentguruPresident

Commented:
That is a good question.

  In the way I was suggesting there would only be one CDS.  Updating of the CDS would be done ONLY through routines that use the critical section.  All this does is act like a traffic cop... only one section of code... from any number of threads... can access it at one time.  Others attempting to access it wait until the critical section is not in use.

  If the call to those methods was done from a thread, and the CDS is connected through a client dataset to visual controls, then these calls would be done through Synchronize to avoid messing up things for the main thread.

  It would simplify things if the CDS was not connected through a DataSource to visual controls.  Then all you would need is an event that could cause the update of the display.

  If you go with Geerts way of doing things then yes, a critical section could also be used to sync up the two datasets.  Just remember that the critical section just acts like a traffic cop to protect the code using the shared resource, it does not do any updating itself.

I hope that helps to clarify.

P.S.
I applaud a person for trying to understand the bare metal programming over, or at least before, just trying to use a library.  Using libraries is great, but only after you understand enough about how they work.  Too many programmers these days, when get to a point where they do not understand how to move forward, assume they either need to 1) obtain controls to do the work for them or 2) that it cannot be done.  How sad for them.

Author

Commented:
Geert /DevelopmentGuru: Thanks to both of you for helpful answers. I've been momentarily derailed fixing bugs from a release, but will be back to this early next week. I'm putting off accepting a solution until I implement a a bit of this and can more fairly say which pieces contributed the most to what works for my situation. But you've both shed helpful light on this. Thanks again.
Geert GOracle dba
Top Expert 2009

Commented:
development guru
visual controls don't call a critical section before accessing the data
not for edit, and not for display

so that's why you need a separate dataset

the gui wll hang and cause av otherwise
Geert GOracle dba
Top Expert 2009

Commented:
there is a cleanup process on this site,
if no activity mlmcc will close this
developmentguruPresident

Commented:
Geert,

  That is a very good point assuming that the delay would be more than, say, a millisecond or so.  It was my understanding that this was a process that would be retrieving, at most, a few emails at a time.  Since the retrieval process is housed in a separate thread, and should be done with it's fetch before ever attempting to notify the client of the update, it should work nearly invisibly.  It is a very good thing to point out the possibility of locking the UI though, I appreciate it.

  The primary thrust of what I wrote was NOT to use the visual controls, in a data aware manner, with this side thread.  It is common practice to use critical sections in any thread, including the main thread, when the situation warrants it.  Here is an MSDN article that speaks to the use of critical sections within the main thread, and side threads, and how to debug issues that you may run into.
Geert GOracle dba
Top Expert 2009

Commented:
i still think you need 2 datasets
if not the thread can accesses the dataset while the gui is editing
since the gui doesn't use a critical section to access the same data

this has nothing to do with timing
it's just basics : a resource is only thread safe if every procedure accessing the resource is locked by a synchronization object (in this case a TCriticalSection)

Author

Commented:
Thanks both to Geert and DevelopmentGuru for the input. DevelopmentGuru, I found your introducing me to the critical section interesting, but given the gui interface need to interact with the cds extensively (as would be expected with email), Geert's solution seemed to fit better. But I appreciate the helpfulness from you both.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial