5

I am trying to show a splash screen, and not freeze up the application, while it connects to a database. Normal connections (to MSSQL via ADO) take about 300 msec, and this does not cause the main thread to show "not responding" on windows.

However in the case of (a) a network error or (b) a configuration mistake (invalid SQL server hostname/instance), it takes 60 seconds to time out. This not only makes the application non-responsive, but it's almost impossible to show any error or message when it's going to freeze. I could pop up a message before I start the connection but there's really no solution when the main thread blocks for 60 seconds.

The solution seems to be to move the connection to a background thread. This lead to the following code:

  1. a TThread-class that makes the background connection and some SyncObj like a TEvent used to send a signal back to the main thread.

  2. A loop in the main thread with this code:

    BackgroundThread.StartConnecting;
    while not BackgroundThread.IsEventSignalled do begin
       Application.ProcessMessages; // keep message pump alive.
    end;
    // continue startup (reports error if db connection failed)
    

Is this the right way to go? My hesitations involve the following elements of the above solution:

A. I would be calling Application.ProcessMessages, which I consider an extreme code smell.(This might be a permissible exception to this rule)

B. I'm introducing threads into the startup of an application, and I'm worried about introducing bugs.

If anyone has a reference implementation that is known to be free of race conditions, that can do a background connection to ADO, and is known to be a safe approach, that would be really helpful. Otherwise general tips or partial examples are good.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • 5
    Given that a normal connection takes only 300ms, why not change the timeout (e.g. 1000ms)? – awmross May 21 '12 at 02:17
  • 4
    How to display splash screen from background thread: http://stackoverflow.com/questions/388506/displaying-splash-screen-in-delphi-when-main-thread-is-busy – Harriv May 21 '12 at 04:28
  • 1
    This issue is one of the main use cases for showing UI from a thread other than the main GUI thread – David Heffernan May 21 '12 at 06:26
  • @David, or if you're not using data aware components move and run database stuff in worker thread(s). This is advisable when you're performing time consuming queries and want to work with form UI for some reason (not only block user with some waiting animation). This model I've successfully used for a tool, which was periodically polling database while user could work with *the rest* of the application. But have to say here would be worker thread form suitable. – TLama May 21 '12 at 06:54
  • 3
    To create a form without dependance of the VCL and good for threads, see [Creating forms without using VCL](http://stackoverflow.com/q/10180016/576719) or [Peter Below's Threaded Splash Screen and Modal Dialogs](http://stackoverflow.com/q/1274270/576719). – LU RD May 21 '12 at 06:55
  • Your while loop will run at 100% CPU. You can make it go idle with MsgWaitForMultipleObjects as per my answer here: http://stackoverflow.com/questions/10334553/processmessages-and-use-of-application – David Heffernan May 21 '12 at 07:02
  • The while loop does not run at 100% CPU because IsEventSignalled does a TEvent.WaitEvent(50) which which means it returns at most 20x a second. This in turn is enough to keep the message loop pumping. (TEvent.WaitForEvent ==> Win32 WaitForSingleObject) – Warren P May 21 '12 at 21:00
  • @awmross; I did that, and it really helps. The only time when it would be a bad thing is if a connection that was going to succeed took 6 seconds. I can't have everything though, can I? :-) – Warren P May 22 '12 at 19:00

2 Answers2

4

Because of the known limitation that every thread must use it's own ADO connection (ie you cannot use a connection object created in other thread), the only option I can think of is create a thread that will make a connection to database, and, after the connection is established or timeout is reached, signal the main thread about the event and close/destroy the connection. The main thread can in the meantime display splash or progress, waiting for a message from that thread. So, you eliminate the case with wrong password or unreachable host. There is a reasonable assumption, that, if the second thread could connect, the main thread would be able to connect right after.

Silver
  • 139
  • 2
  • +1, agree, but you would need to move all database actions into that thread, because you cannot pass the connection object to the main thread from your worker one. And it's necessary to have them in a main thread e.g. in case you are using data aware components. – TLama May 22 '12 at 11:22
  • 1
    He's saying that you only open and then just close that connection. So you are only handling an initial connection as an "error checking" feature of your app. You still have to re-do the connection logic in the main thread. – Warren P May 23 '12 at 16:01
  • @Warren, if you are going only to verify if it's even possible to connect and later re-connect with the same connection settings in the main thread then you could use the code from my deleted (now really deleted) answer. – TLama May 24 '12 at 14:02
1

There are several methods for inter-thread communication. I usually use Window's messages. First I define custome message and message handler in a form. Then when I need to inform main thread from custom thread, I use PostMessage function to send notification.

Small tutorial here.

You can also use library for threading, eg OmniThreadLibrary.

Harriv
  • 6,029
  • 6
  • 44
  • 76
  • I know. BUt I'm wondering specifically about blocking my app startup -- how do I do it - my own message-pump looping (calling application.process messages) or something else? – Warren P May 22 '12 at 18:59
  • I ended up displaying splash screen in thread.. See link in my comment to question. – Harriv May 24 '12 at 20:37