0

I`m using Delphi XE6 and UniDAC and MySQL

I have some TUniQuery components in my DM and I want to Refresh theme repeatedly, so I put some Timers in my main form and in each timer I create a thread and pass a query to it for refreshing data :

for Example :

 TUpdateThread = class(TThread)
 private
  FQuery : TUniQuery;
  FResultHandle : THandle;
 public
  constructor Create(var Query : TUniQuery; ResultHandle : THandle);
 protected
  procedure Execute; override;
 end;

constructor TUpdateThread.Create(var Query: TUniQuery; ResultHandle : THandle);
begin
 inherited Create;
 Suspend;
 FQuery := Query;
 FResultHandle := ResultHandle;
 FreeOnTerminate := True;
 Resume;
end;

procedure TUpdateThread.Execute;
var
 Msg : String;
 B : Boolean;
begin
 try
  B := True;
  try
   FQuery.Refresh;
  except
   on E:Exception do
    begin
     B := False;
     Msg := 'Error : ' + #13 + E.Message;
     SendMessage(FResultHandle, MSG_UPDATERESULT, 2, Integer(Msg));
    end;
  end;
 finally
  if B = True then
   SendMessage(FResultHandle, MSG_UPDATERESULT, 1, 1);

  Terminate;
 end;
end;

Sometimes it`s done successfully but many times I got some errors such as AVs or "Net Pack Header ... " error or sometimes I have problem in my Grids ( Ehlib DBGrid ) such as error in drawing rows or ... ( specially when I use DisableControls and EnableControls ) All of Queries have same connection , I think each Thread should have his own connection, because of all timers intervals are same , I suggest sometimes refreshing queries interrupts each others

In fact, my database is in a VPS server and there is some client applications , I want to have Live-Tables in Clients and update theme repeatedly

What is the best way to achieve that ? how I should update my Tables without application hangs ! there is some components as TThreadTimer ( or ... ) , is theme useful for this situation ?!

thanks ...

Mahmoud_Mehri
  • 1,643
  • 2
  • 20
  • 38
  • 3
    Do not use suspend/resume. These methods are deprecated. Instead create the thread suspended and `start` it when initialization is done – Johan Sep 22 '15 at 12:05
  • 2
    Do not `SendMessage` from your thread. Consider what happens if the receiver stucks on its processing. Either use `PostMessage` or `SendMessageTimeout` to make sure your thread won't die on that line. That's why `Synchronize` is not implemented as just this single function call. – TLama Sep 22 '15 at 13:33
  • @TLama : but Postmessage sent message in messae queue and have delay ! , many times when I create a thread , I show a waiting window and there is no job until message arrives – Mahmoud_Mehri Sep 22 '15 at 17:27
  • Sorry, but I don't get your last comment. The difference between `PostMessage` and `SendMessage` is that the first posts the message to the queue and continues whilst the latter enqueues the message and waits until it's processed. And if that processing never ends, you'll stay blocked on that `SendMessage` line forever. – TLama Sep 22 '15 at 19:02

1 Answers1

1

The first issue is here :

constructor TUpdateThread.Create(var Query: TUniQuery; ResultHandle : THandle);
begin
 inherited Create;  // Create with no arguments 
 Suspend;           // means CreateSuspended = false
 FQuery := Query;
 FResultHandle := ResultHandle;
 FreeOnTerminate := True;
 Resume;
end;

Here you create the thread with the default constructor (CreateSuspended = false) where the thread begins running immediately. You call suspend (which is deprecated and should not be used) immediately, but this is still a race condition since your thread may or may not start trying to Refresh your query before you've assigned it. To create the thread in a suspended state use the overload constructor of

inherited Create(true);

Resume is also deprecated. Instead you should use Start;.

Further, you're passing in a TUniQuery to this thread's constructor. We can assume, I imagine, that this query has affinity to the main thread - this is to say that it is (perhaps) a visual component on a form, has databindings to visual components, or is otherwise interacted with by the user or user interface.

The answer, if so, is that you simply cannot do this - a thread cannot modify an object with affinity to another thread. Your interface may be in the middle of retrieving records from the query when the background thread, for example, is simultaneously destroying them in preparation to refresh the query contents. Naturally this will cause all sorts of problems.

The simple solution is to use a regular timer and refresh synchronously on the main thread. If this takes too long then you need to consider a different strategy altogether. We don't really have sufficient information to suggest much further. If network access and I/O is the bottleneck then you might consider asynchronously refreshing to a separate query object owned by the thread, then synchronously assign it to your view components.

J...
  • 30,968
  • 6
  • 66
  • 143
  • 1
    In other words, create and initialize the UniQuery inside the thread, and upon thread termination hand the UniQuery over to the form. In that case you would just pass the SQLtext to the Thread as paramater. – Johan Sep 22 '15 at 12:21
  • 1
    Maybe worth mentioning that ClientDataSets provide a simple way to transfer data between a worker thread and the VCL one, by assigning the worker thread's CDS Data property. – MartynA Sep 22 '15 at 14:24
  • I have tried using Start , but when I create thread an Error Shown : "cannot call Start in a running or suspended thread" , what goes wrong ?! – Mahmoud_Mehri Sep 22 '15 at 17:16
  • found it ! , Start should call like this : "TMyThread.Create(...).Start;" , A Thread cant Start itself ! – Mahmoud_Mehri Sep 22 '15 at 17:24