3

Create a VCL Forms Application, put a TButton and a TMemo on the Form, and write this code in the button's OnClick handler:

uses
  OtlParallel, OtlTaskControl;

procedure TForm2.btnStartLoopClick(Sender: TObject);
var
  starttime: Cardinal;
  k: Integer;
begin
  mmoTest.Lines.Clear;
  for k := 1 to 50 do
    mmoTest.Lines.Add('Line ' + IntToStr(k));

  starttime := GetTickCount;
  Parallel.Async(
    procedure
    var
      i: Integer;
    begin
      for i := 1 to 50 do
      begin
        Sleep(100);
        mmoTest.Lines[i - 1] := mmoTest.Lines[i - 1] + FormatDateTime(' nn:ss:zzz', Now);
      end;
    end,
    Parallel.TaskConfig.SetPriority(TOTLThreadPriority.tpHighest).OnTerminated(
    procedure
    begin
      mmoTest.Lines.Add(IntToStr(GetTickCount - starttime) + ' milliseconds');
    end));
end;

Now run the program and make this test:

  1. Click on the button, simply wait for the loop to complete and look at the time displayed in the last line of the memo: It should be approximately 5300 milliseconds.

  2. Now click again on the button, click and hold the form's title bar and move the form around quickly until the loop has finished. Now look again at the memo's last line: In my tests, the time was over 7000 milliseconds. Obviously, the main thread is blocking the parallel thread!

So how can the main thread blocking the parallel thread be avoided?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
user1580348
  • 5,721
  • 4
  • 43
  • 105
  • 1
    - "Obviously, ..." - Why? First, I'd suggest a test that you open any cpu usage monitoring program and then hold any window's title bar and move quickly while observing how does that effect cpu usage. If it is anything significant, then your assumption is moot. – Sertac Akyuz Jan 04 '17 at 23:40
  • Remy, could you give a small code example? – user1580348 Jan 04 '17 at 23:42

1 Answers1

10

First, this code is not thread-safe, as the asynchronous code is directly accessing the TMemo from a task thread outside of the main UI thread. You cannot do that. A worker thread MUST synchronize with the UI thread in order to access UI controls safely or else bad things can happen. You can use TThread.Synchronize(), TThread.Queue(), or IOmniTask.Invoke() for that synchronization.

Second, while you are holding down the mouse on the title bar, the main UI message loop is blocked (a separate modal message loop is being run by the OS until you let go of the mouse). As such, the task's OnTerminate event handler may not be run until the main message loop regains control. That would account for why your timer duration is reportedly longer than expected, not because the task loop was blocked.

Third, Sleep() is not absolute. It will sleep for at least the requested amount of time, but may sleep for longer. So your task loop will run for at least 5 seconds, but may be a little longer.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • About "accessing the TMemo from a task thread outside of the main UI thread": I assume this rule is valid only for the worker thread MODIFYING a UI control, not for READING a value from a UI control? – user1580348 Jan 05 '17 at 00:04
  • 5
    @user1580348: No. **ANY** access, reading or writing, must be synchronized. And the main reason for that is because the `TWinControl.Handle` property is not thread-safe, it can potentially change value while a separate thread is using the `HWND`, which causes all kinds of problems. So, even reading of data that is tied to the `HWND` must be protected. – Remy Lebeau Jan 05 '17 at 00:48
  • 1
    You can also `PostMessage()` and a custom message to send the string that will be displayed in the memo. – Nat Jan 05 '17 at 05:07
  • 1
    @Nat only if you post it to an `HWND` that is guaranteed to not be reallocated dynamically while the task thread is running, such as `TApplication.Handle`, or an `HWND` you allocate yourself using `AllocateHWnd()` or `CreateWindow/Ex()` directly. The message handler can then add the received string to the Memo. – Remy Lebeau Jan 05 '17 at 07:40
  • But what if the program logic and program flow surely EXCLUDE that another thread accesses the UI control at the same time as the worker thread? Shouldn't accessing the UI control from the worker thread be allowed in this case? – user1580348 Jan 05 '17 at 13:37
  • 1
    @user1580348 no, because the main UI thread itself still touches the control at random times, such as in response to OS messages. And the control's `Handle` property value can be recreated dynamically at any time. If a worker thread touches the control after a recreate is started and before the new `HWND` is ready, really bad things happen. It is a race condition with nasty consequences. So best to avoid it. – Remy Lebeau Jan 05 '17 at 16:12
  • @RemyLebeau Fair enough. If it is only a UI update, then perhaps you don't need to worry too much. Just make sure the any new handle gets passed to the thread when it changes. Otherwise, I often give my thread a window handle from `AllocateHWnd()` to `PostMessage()` to, then I call an event back to the main form. – Nat Jan 09 '17 at 00:27