2

I'm studying these two examples from these questions

What techniques I was using to debug?

  • Read the code and speculate how it is going to fail.

  • Compile the code and let it run and see if I'm right.

That got me to find a bug in the second answer. This was my comment:

The example you have shown has a bug: If you let the task finish it will show task terminated on the label and not task done because you are calling btnCancelTaskClick(Self); after the label's caption is set to task done which will destroy the thread which will wake up the cancel event and breaks the loop resulting in posting the second message which will change the caption to task terminated. the solution would be to add this line if not readyFlag then before the call of the second postmessage. I have suggested an edit I hope you do not mind

What techniques am I using now?

  • First techniques.
  • Put break points everywhere and press Ctrl + Alt + T to see the stat of each thread.

Now here were things start showing its ugly side. I come to understand that threads run in a non synchronized way (which runs first will remain a ???). and these are the results I got from the first answer.

Example to reproduce

Create a new VCL project and add two TButtons on the main form. The first button creates the thread an the second one frees it.

procedure TForm6.Button1Click(Sender: TObject);
begin
th.Free;
end;

procedure TForm6.Button2Click(Sender: TObject);
begin
  th := TMyThread.Create;
end;

press Button2 then Button1 and see what you can get

Results (under debugger):

  • In the first run I got GetLastError = 6 = ERROR_INVALID_HANDLE. strangely the result of the TEvent.WaitForMultiple was not wrAbandoned when the TEvent was destroyed in the Destructor from the main thread (the main thread is the one executing the destroy code).

  • In the second run I got nothing and the thread was terminated properly.

  • In the third run the code stuck on the CheckPause method, because the TEvent.WaitForMultiple returned wrAbandoned and it kept looping inside until terminated was True.

When launching the second answer without the debugger nothing happened for 10 runs.

Conclusions: I admit debugging a multi-threaded application is a nightmare. The best way I can design a proper non deadlocking free of bugs class is also a nightmare.

Checking for terminate will be my mode from now.

The errors I get in the debugger are not present always when I launch the code out of it (no idea why? I checked if the exceptions are swallowed some where else but with no success).

Question How can I improve the debugging process in a Delphi multithreaded code? So I get more of these errors.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Nasreddine Galfout
  • 2,288
  • 2
  • 12
  • 32
  • 3
    My 2 cents, add logging, lots of logging in each thread and have a framework that supports multithreaded logging. It saved me multiple times... – whosrdaddy Nov 09 '17 at 17:15
  • @whosrdaddy thank you, I will do a search on that. – Nasreddine Galfout Nov 09 '17 at 23:08
  • 2
    I don't have much to suggest that would fit in an SO Q&A. I realized long ago that stopping/cancelling/terminating etc threads in any language is a design fail, leads to debugging nightmares and, since Delphi 3, (a long time ago), I have always avoided trying, (ie. by using app-lifetime threads, pools with P-C queues for input etc). If you design apps that completely avoid the stuff that you are struggling to debug, you won't have to debug them. That is not a Delphi thing it's a multithreading thing. – Martin James Nov 13 '17 at 12:12
  • 1
    Delhi thread support has been umm.. 'non-optimal' since D3. TThread.Synchronize has been rewritten at least twice. TThread.WaitFor() is a deadlock waiting to happen. Delphi got thorugh decades of development without a producer-consumer queue class, (though one can easily be knocked up as a TQueue descendant), and with tutorials that demostrated only create/terminate/waitfor rubbish design. 'Intro to Thread' websites targeting other languages are equally sucky:( – Martin James Nov 13 '17 at 12:19
  • @MartinJames I spent more than a month reading about how to do multithreading. and the result was a series of deadlocks and bugs, so I thought I could be more immune if I spend more time in debugging the errors I get. so I can avoid them in future projects – Nasreddine Galfout Nov 13 '17 at 12:23
  • 1
    Why TForm never had a 'TForm.ThreadMessage(AOwner:TObject;mess:TThreadMessage) event, someting to receive a 'TThreadMessage' object posted, (on Windows, PostMessaged), to a form instance from a thread, I cannot fathom. Maybe Borland/Emba decided that users could not handle the object lifetime issues, and thought that was more difficult than create/terminate/WaitFor, (it's not). – Martin James Nov 13 '17 at 12:28
  • 1
    @NasreddineAbdelillahGalfout For Windows GUI apps, I use mostly message-passing. P-C queue from GUI threads/forms to work threads, PostMessage back. Other than at app startup, I don't create threads during the app run and don't terminate/WaitFor them. That means that I don't have to debug those things, and I don't get difficult bugs, I don't get deadlocks and my apps close down immediate when I click on the close icon. That isn't because I'm a better programmer than you, it's because I ignored the Delphi threading examples all that time ago, at D3, when I found out how bad they were. – Martin James Nov 13 '17 at 12:36
  • @MartinJames I'm starting to have the same attitude as you did. some of those examples made me go even deeper in the wrong way. thank you for sharing this with me. I really appreciate it. – Nasreddine Galfout Nov 13 '17 at 12:46

0 Answers0