Search code examples
delphithread-safetycritical-sectionindy10

Will a Critical Section help solve this?


Using an Indy 10 TCP command handler, every time I get a command I insert a row into a database and then I read the entire tale from the database to update a string grid.

I am using AnyDac database components and their documentation says "A connection object and all associated with it objects (like TADQuery, TADTransaction, etc) in each moment of time must be used by a single thread".

If I send the TCP commands "slowly" there is no problem. If I send them "quickly" (while AnyDac is still showing the SQL cursor, I get an exception EDatabaseError "field not found" - but it exists, of course.

When I receive a TCP command, my code sends a UM_ using PostMEssage to my main form.

What I think is happening is that the main form is reading all the rows from a table as a result of the first TCP command while the TCP command handler is half-way though inserting a new row as a result of the second command - hence "field not found" when I invoke ADQuery1.FieldByName().

Does that sound like the problem?

If so, how can I prevent it? Can use a Critical Section (where, main thread?)? Or is there some other way?


[Update] I just realized - this cannot be a threading problem (I think). When I get a TCP command I send a UM_ to my main form using PostMessage(). So, no matter how quickly the TCP Commands come, my main form can only process one UM_ at time via its message queue. The TCP command handlers are just a single line to send that message - no d/b access.

BUT - what I don't understand is that if leave "some time" between TCP commands all is well, but if I send them "quickly" then I get the exception saying that there is no such field in that row of the table.


[Update] In fact I finally solved it and the problem was using the rather slow-to-update TStringGrid. Tehre are ways to make it quicker, but I decided to convert it to a TDbGrid, which updats extremely quickly.


Solution

  • It sounds like you are updating the AnyDac database from a TCP command handler running in a background thread, and reading from the AnyDac database from the main thread. Since AnyDac says they don't support multithreaded access, this will create problems.

    One option is to try to make all the participating threads line up and wait to access the AnyDac database. This runs against the goal of using multiple threads to begin with - why use multiple threads if all you're going to do is make them line up and execute sequentially? Adding mutex locks (like critical sections) increases the chances of you creating a deadlock situation, especially if you plan to acquire that lock in the redraw logic of your app. You absolutely don't want locks in your redraw logic.

    A different approach would be to move the database update into the main thread. There is still some thread synchronization involved to do this, but the onus is on the background thread, not on your main thread.

    One relatively simple way to move the database update into your main thread is to have your TCP command handler post a custom UM_ message to the main window (as you are already doing) and attach the data to the message. The message handler picks the data up off the message, updates the database with the data, and disposes of the data that was attached to the message.

    With this approach, all access to the AnyDac database objects occurs in the main thread, so there is no multithreading issue. Unless somebody is doing something silly like calling Application.ProcessMessages in the middle of a redraw cycle, this should resolve any issue with database updates happening during redraws.