Jump to content
Jud

Problem writing to memo on main form from thread

Recommended Posts

I need to write to a memo on the main form from within a thread.  In an old program using tasks I was doing this:

 

tThread.synchronize( tThread.current, procedure
begin
  form1.TestMemo.lines.add( 'Testing ');
  application.ProcessMessages;
end);

 

and it was working.  I put the same type of thing in a tread called by the parallel for, but it isn't writing to the memo. 

 

Is there a different way to do it from a thread called from a parallel for? 

Share this post


Link to post

I think that "ProcessMessages" it's not necessary.

you can use "Synchronize" (wait me ...)  or "Queue" (dont wait me ...)

Edited by programmerdelphi2k

Share this post


Link to post

Replace application.processmessages with form1.TestMemo.Update

 

...and then Google "avoid application.processmessages", "never use application.processmessages", "don't use application.processmessages", etc.

 

  • Like 1

Share this post


Link to post

Why would we even need to call Update. I think I'd want to understand that first, because it's probably indicative of a design flaw. 

Share this post


Link to post
1 hour ago, Jud said:

I need to write to a memo on the main form from within a thread.  In an old program using tasks I was doing this:

 

tThread.synchronize( tThread.current, procedure
begin
  form1.TestMemo.lines.add( 'Testing ');
  application.ProcessMessages;
end);

 

You should remove Application.ProcessMessages from that code as it serves no purpose. 

 

1 hour ago, Jud said:

and it was working.  I put the same type of thing in a tread called by the parallel for, but it isn't writing to the memo. 

 

Is there a different way to do it from a thread called from a parallel for? 

 

TParallel.For is blocking call and that means it will block the thread from which it is called. When you call it from the context of the main thread it will block the main thread. TThread.Synchronize and TThread.Queue procedure can only work in coordination with main thread which periodically calls (this is a bit simplified explanation) CheckSynchronize procedure that will run any pending code that requires synchronization. If the main thread is blocked - not executing any code as it is waiting for TParallel.for to finish it cannot run CheckSynchronize and it will deadlock if you use TThread.Synchronize in any code that is called inside TParallel.For because Synchronize itself is also blocking call. TThread.Queue is not blocking and code will not deadlock, but it is still not a solution because all code inside will run after TParellel.For completes.

 

Additionally, TParallel.For will cause your application to be unresponsive for the whole time it is running, and because of that it should never be called from the main thread. If you need to parallelize some task, run additional thread that will call TParralel.For and you will be able to use either Synchronize or Queue again, and your UI will not be blocked while you are running your parallel task.

Share this post


Link to post

@Jud

 

try this way if works for you

  • using "Queue" no order will be used, but in "Parallel" you cannot control the flow (calls) too (by default)
implementation

{$R *.dfm}

uses
  System.Threading;

var
  LTask: ITask;

procedure MyProcToParallelFor(AValue: integer);
begin
  TThread.Queue(nil,
    procedure
    begin
      Form1.Memo1.Lines.Add(                                                       { }
        Format('Thread: %d, AValue: %d', [TThread.CurrentThread.ThreadID, AValue]) { }
        );
    end);
  //
  sleep(50); // for responsive tests...
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LStart, LEnd: integer;
begin
  LStart := 1;
  LEnd   := 1000;
  //
  LTask := TTask.Create( { }
    procedure
    begin
      TParallel.For(LStart, LEnd, MyProcToParallelFor);
    end);
  //
  LTask.Start;
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := (LTask = nil);
  //
  if not CanClose then
    CanClose := not(LTask.Status in [TTaskStatus.Running, TTaskStatus.WaitingToRun, TTaskStatus.WaitingForChildren]);
  //
  if not CanClose then
    Caption := 'Task in process... ' + TimeToStr(now);
end;

initialization

ReportMemoryLeaksOnShutdown := true;

 

bds_1ja0uIvl43.gif

Edited by programmerdelphi2k

Share this post


Link to post
6 hours ago, programmerdelphi2k said:

I think that "ProcessMessages" it's not necessary.

you can use "Synchronize" (wait me ...)  or "Queue" (dont wait me ...)

5 hours ago, Dalija Prasnikar said:

 

You should remove Application.ProcessMessages from that code as it serves no purpose. 

 

I took that out. 

Also, I just realized that I made a mistake.  I was using tThread.synchronize in one place and tThread.queue in another place, writing to the same memo, and the data was not matching.  It seems that thread.queue is NOT thread-safe for doing this.

 

It is working now, thanks everyone!

 

Share this post


Link to post
38 minutes ago, Jud said:

. It seems that thread.queue is NOT thread-safe for doing this.

"Queue" put on queue and go back to next command-line (code)... then when possible, your command (queued) will be executed.

Quote

Queue causes the call specified by AMethod to be asynchronously executed using the main thread, thereby avoiding multi-thread conflicts.

Quote

If you are unsure whether a method call is thread-safe, call it from within the Synchronize or Queue methods to ensure that it executes in the main thread.

Quote

Unlike Synchronize, execution of the current thread is allowed to continue. The main thread will eventually process all queued methods.

 

https://docwiki.embarcadero.com/Libraries/Alexandria/en/System.Classes.TThread.Queue

Edited by programmerdelphi2k

Share this post


Link to post
10 hours ago, Jud said:

Also, I just realized that I made a mistake.  I was using tThread.synchronize in one place and tThread.queue in another place, writing to the same memo, and the data was not matching.  It seems that thread.queue is NOT thread-safe for doing this.

TThread.Synchronize is blocking call and it will block until the code runs in the context of main thread. TThread.Queue adds that code in a queue and returns immediately and the code will run some time in the future.  Besides those differences both procedures do the same thing, run the code in the context of main thread and are thread safe.

 

Depending on which ones of those methods you call and in which order the output you will get can differ in order but will be deterministic in a way that you can know how that order will look like by reading the code. However, that exact order is only valid for single thread. When you have multiple threads running in the background (and with TParallel.For you will commonly have more than one) the order in which those multiple threads execute synchronized code can be interleaved and output from one thread can appear in between the output from another thread.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×