1

Using: Delphi XE2, Windows VCL Forms application

Can a TThread during its execution change the value of a variable in the main VCL thread?

The need is to update an Integer which is declared as a field of the TForm class. It will be passed to the TThread as a var variable in an overloaded (and reintroduce) Create constructor method.

Are there any fallbacks in this?

Steve F
  • 1,527
  • 1
  • 29
  • 55
  • There are literally thousands of articles, questions, and tutorials on this subject. What have you found so far? Your approach of passing this variable into your thread is wrong. You need to synchronize events to change the value, or other approaches such as using messages. – Jerry Dodge Feb 04 '15 at 05:03
  • I'm also not sure how passing a var parameter into the thread's constructor would allow the thread to update that variable (other than from within the constructor). You would have to have a pointer to that integer to do that, which is still not safe since that variable is part of the VCL. – Jerry Dodge Feb 04 '15 at 05:12
  • possible duplicate of [Thread-safe in delphi](http://stackoverflow.com/questions/17705197/thread-safe-in-delphi) – Jerry Dodge Feb 04 '15 at 05:17
  • 2
    @JerryDodge if the int is just a field in a Form then it isn't technically VCL - it's just a class field. Critical section or other sync obj would be fine. – J... Feb 04 '15 at 10:18
  • Don't use **reintroduce**: 1) The feature was added for an exceptionally narrow use-case. And even there, one should rather make every effort to **not** hide the ancestor method rather than hiding the fact that there's a problem. 2) In you particular case, your thread needs to be given all relevant information to do its job. `TThread.Create` is not virtual. So rather declare a single stock standard `constructor Create();` and that's it. Write your threads constructor to take all it needs: _nothing more and nothing less._ It will be much easier to maintain that way. – Disillusioned Feb 04 '15 at 15:24

1 Answers1

10

Yes, threads can modify variables. Variables don't belong to threads. Variables can belong to form or thread objects, but a thread object (i.e., an instance of TThread or its descendants) is distinct from the OS execution thread.

Objects can have code that runs in multiple threads. Your TThread.Create method runs in the context of the thread that calls it, which is often your main thread. The Execute method, on the other hand, runs in the context of the created OS thread. But obviously, both methods can access the fields of the TThread object, so that answers the question of whether two OS threads can access the same variable.

You'll have trouble accessing the form variable in the way you describe, though. Passing it to the constructor as a var parameter will allow the constructor to modify it, but as I mentioned above, the constructor doesn't run in the context of the new OS thread. To allow the new thread to access that variable, you'd need to store a pointer to it instead of passing it by reference. For example:

type
  TSteveThread = class(TThread)
  private
    FVariable: PInteger;
  protected
    procedure Execute; override;
  public
    constructor Create(Variable: PInteger);
  end;

constructor TSteveThread.Create;
begin
  inherited Create(False);
  FVariable := Variable;
end;

procedure TSteveThread.Execute;
begin
  // Access FVariable^ here.
end;

Create it like this:

procedure TSteveForm.ButtonClick;
begin
  TSteveThread.Create(@Self.Variable);
end;

An alternative is to pass a reference to the form instead, and then access the form's field through that reference. For example:

type
  TSteveThread = class(TThread)
  private
    FForm: TSteveForm;
  protected
    procedure Execute; override;
  public
    constructor Create(Form: TSteveForm);
  end;

constructor TSteveThread.Create;
begin
  inherited Create(False);
  FForm := Form;
end;

procedure TSteveThread.Execute;
begin
  // Access FForm.Variable here.
end;

Create it like this:

procedure TSteveForm.ButtonClick;
begin
  TSteveThread.Create(Self);
end;

In either case, you need to take the usual precautions about controlling simultaneous access to the data by multiple threads. The bottom line is that both threads can access the data.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I was thrown off for a second how the declaration of `Create` had the parameter but the `Implementation` part did not. Then I remembered, while it's confusing, it is valid code. To avoid confusion, I always make the signature of the `Implementation` perfectly match that declared in the object. – Jerry Dodge Feb 04 '15 at 05:31
  • Isn't your second alternative solution breaking the rules majorly? That's an example of how not to do it. – Jerry Dodge Feb 04 '15 at 05:32
  • Me too, @Jerry, but I'm typing this on my phone, so I don't want to bother with more than I need. – Rob Kennedy Feb 04 '15 at 05:32
  • I see no rules being broken. [Citation needed]. – Rob Kennedy Feb 04 '15 at 05:33
  • 2
    That's the worker thread making direct changes to the VCL main thread without any protection at all. That's what causes the race condition(s). In fact, the first example also doesn't have protection either, such as a critical section. – Jerry Dodge Feb 04 '15 at 05:34
  • 1
    It's only a race condition if there's potential for both threads to use it simultaneously without other considerations. Critical sections, interlocked operations, and any number of other context-dependent techniques can be used to avoid it, which my last paragraph mentions. My two examples do not differ at all in that respect, @Jerry. – Rob Kennedy Feb 04 '15 at 05:38
  • Your last paragraph addresses modifying pointers to the values rather than the values themselves. But two threads accessing the same block of memory at the same time is not addressed. The VCL is subject to change these variables without you even knowing, leading to potential race conditions. This is why it's highly advised to synchronize VCL updates. – Jerry Dodge Feb 04 '15 at 05:41
  • Actually by "last paragraph" I was referring to your third paragraph before the code started - but then I saw the very very end paragraph. I think OP is asking for how to protect these variables. Passing an integer as a `var` parameter is just another issue on top of that which OP probably never thought about. – Jerry Dodge Feb 04 '15 at 05:44
  • When I refer to "the data," @Jerry, *that's* the "block of memory" you're worried about. We can modify the value of `FVariable` at will and nothing will care because we can safely assume (or define) that nothing but the thread object that owns it will see it, but accessing `FVariable^` — the shared data being pointed at by the pointer — requires precaution. – Rob Kennedy Feb 04 '15 at 05:46
  • I believe OP has two issues, and each of us are looking at it differently. I interpreted the question as "safely share a VCL variable with another thread" while you interpreted it as "pass a VCL variable into the thread". So I see it as the very last paragraph being the only part of the answer relevant to what OP was looking for (although the rest is helpful too). – Jerry Dodge Feb 04 '15 at 05:48
  • @Jerry, I saw the question as "Here's how I plan to grant access to the variable, but even if it works, I'm not sure it can really be modified." I answered that it can be modified, but the proposed method of passing it in wouldn't work. I chose not to address the second question (about "fallbacks") in depth because I think it's too broad to be a "rider" on the main question. – Rob Kennedy Feb 04 '15 at 05:54
  • Which is why I voted to close as a duplicate rather than answering :-) Answering vague questions or questions with multiple issues always leads to this type of thing. Reappointed my +1. – Jerry Dodge Feb 04 '15 at 05:55
  • A pointer with critical sections was the obvious solution. Answer accepted. – Steve F May 27 '16 at 19:00