2
votes

I'm getting an exception in an OnTimer event handler (TTimer) that when executed increments an integer variable in the parent form. The timers need to be able to access an incremented integer used as an id.

My first question is: How can I tell in Delphi 2007 which code is running in which thread? Is there a way in debug mode to inspect this so I can determine for sure?

Secondly, if I need to access and modify variables in a parent form from another thread, what is the best way to do that? It seems like sometimes Delphi allows me to access these variables "incorrectly" without giving an exception and other times it does give an exception.

4
Could you clarify a little bit, which execeptions do you get? Can you show some code?Harriv
You have two questions, so ask them separately. You'll get better, more focused answers, and you'll have the opportunity to get twice the reputation points.Rob Kennedy

4 Answers

5
votes

Just to be sure: On one hand you are talking about a timer event, on the other about multithreading. Those are two totally different ways of running code in parallel.

A timer will always be run in the main thread. It should be safe there to access everything that was created and is being used in the main thread. In fact, a timer event can only occur, when no other main thread code is running, because it needs the application's message handler to process the timer message. So it is either outside of any event handling code or when one of your event handlers calls Application.ProcessMessages.

A thread is very different from this. In this case, the code in different threads runs independently from each other. If running on a multi-processor machine (or multi core), it is even possible they truly run in parallel. There are quite a few issues you may have this way, in particular the Delphi VCL (up and including Delphi XE) is not thread save, so calls to any VCL class must only be done from the main thread (there are a few exceptions to this rule).

So, please first clarify whether you are talking about timers or true multithreading, before expecting any useful answers.

3
votes

How can I tell in Delphi 2007 which code is running in which thread? Is there a way in debug mode to inspect this so I can determine for sure?

You can set a breakpoint and when execution stops look at the threads debug window. Double click on each thread to see its callstack in the callstack debug window. You can also use the Win32 function GetCurrentThreadId to find out about the current thread (e.g. for logging, or to determine if the current thread is the main thread etc).

Since you are not showing any code it is hard to be more specific. Just to be sure: code in a timer event handler is not getting executed in a different thread. You won't have concurrent-access issues if you are just using timers, not real background threads.

Secondly, if I need to access and modify variables in a parent form from another thread, what is the best way to do that? It seems like sometimes Delphi allows me to access these variables "incorrectly" without giving an exception and other times it does give an exception.

If you really are in another thread and access a shared variable you can see all sorts of things happening if you don't protect that access. It might work ok most of the time or you get strange values. If you just want to modify an integer in a thread-safe manner, look at InterlockedIncrement. Otherwise you could use a critical section, mutex, monitor... JEDI has some useful classes in the JclSynch unit for that.

3
votes

You are asking two questions, so I'll answer them in two answers.

Your first question is about using TTimers; those always run in the main thread.

Most likely, your exception is an access violation.

If it is, it is usually caused by either of these:

  • a- your parent form is already destroyed when your TTimer fires.
  • b- your do not have a reference yet to your parent form when your TTimer fires.

b is easy: just check if your reference is nil.

a is more difficult and depends on how you reference your parent form.

Basically you want to make sure your reference gets nil when the parent is being destroyed or removed.

If you reference your parent form through a global variable (in this example through Form2), then you should have TForm2 make the Form2 variable nil using the OnDestroy event like this:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm2 = class(TForm)
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.FormDestroy(Sender: TObject);
begin
  Form2 := nil;
end;

end.

If you are using a field reference to your parent form (like FMyForm2Reference), then you should use add a Notification method like this:

unit Unit1;

interface

 uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Unit2;

 type
  TForm1 = class(TForm)
  private
    FMyForm2Reference: TForm2;
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
  end;

 var
  Form1: TForm1;

 implementation

{$R *.dfm}

 procedure TForm1.Notification(AComponent: TComponent; Operation: TOperation);
 begin
  inherited Notification(AComponent, Operation);
  if (Operation = opRemove) then
    if (AComponent = FMyForm2Reference) then
      FMyForm2Reference := nil;
 end;

 end.

Regards,

Jeroen Pluimers

2
votes

You are asking two questions, so I'll answer them in two answers.

Your second question is about making sure only 1 thread accessing 1 variable in a form at a time.

Since the variable is on a form, the best way is to use the Synchronize method for this.

There is an excellent example about this which that ships with Delphi, it is in the thrddemo.dpr project, where the unit in SortThds.pas has this method that shows how to use it:

procedure TSortThread.VisualSwap(A, B, I, J: Integer);
 begin
  FA := A;
  FB := B;
  FI := I;
  FJ := J;
  Synchronize(DoVisualSwap);
 end;

Good luck,

Jeroen Pluimers