Jud 1 Posted July 6, 2023 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
programmerdelphi2k 237 Posted July 6, 2023 (edited) I think that "ProcessMessages" it's not necessary. you can use "Synchronize" (wait me ...) or "Queue" (dont wait me ...) Edited July 6, 2023 by programmerdelphi2k Share this post Link to post
Anders Melander 1784 Posted July 6, 2023 Replace application.processmessages with form1.TestMemo.Update ...and then Google "avoid application.processmessages", "never use application.processmessages", "don't use application.processmessages", etc. 1 Share this post Link to post
David Heffernan 2345 Posted July 6, 2023 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
Dalija Prasnikar 1396 Posted July 6, 2023 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
programmerdelphi2k 237 Posted July 6, 2023 (edited) @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; Edited July 6, 2023 by programmerdelphi2k Share this post Link to post
Jud 1 Posted July 6, 2023 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
programmerdelphi2k 237 Posted July 7, 2023 (edited) 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 July 7, 2023 by programmerdelphi2k Share this post Link to post
Dalija Prasnikar 1396 Posted July 7, 2023 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