Procedure and START it in 2 Threads

Hello All
On the main “main” procedure (declared as a window) I put a button and this code below:

START(MyProcedure, 35000, 1, 1) ! start a thread for user 1 with parameter 1
START(MyProcedure, 35000, 2, 3) ! start a thread for user 2 with parameter 3

MyProcedure, declared as (Window)
MyProcedure (string userID, string waitingTime)

code (for simulating work on the database)

 loop L = 1 to (wait time * 100)
   ! some database process...
   0{prop:text} = 'userID: '&userID&' Time: '&waitingTime&' L Value: '&L
 end

I expect to see the procedure with parameter 1 as the first procedure run, but as you can see, the procedure with user ID “2” is run first.
Not every time, sometimes userID “1” starts as the first one, there is no rule.
Another thing is that even if the procedure is started with the parameter “1”, it does not start until the previous procedure is finished.
I need the both procedures to start processing immediately, how do I achieve this?

loop

Hi @Daniel1

Firstly, let’s clear up the usage of START. From the help:

START(procedure [, stack ] [, passed value ] )

The second parameter in your START call is the size of the stack to allocate, not a thread ID. You can pass a maximum of three strings as parameters, which isn’t an issue in your case.

Now, regarding thread execution order: I presume you are using a version of clarion that is v6 or above which uses preemptive threading. Threads should theoretically run concurrently without waiting for each other to finish. However, (I think but someone correct me), the operating system’s thread scheduler decides which thread to give CPU time to at any given moment, so the execution order can vary, which is why sometimes you see userID 2 starting first, and other times userID 1.

Lastly, be cautious about starting too many threads in rapid succession. While Clarion handles threads preemptively, rapidly opening many threads can still cause issues and crash your program. In this can I use a NOTIFY to notify the parent to start the next thread etc.

Mark

2 Likes

The Clarion runtime handles some of the api parameters for you, so you only have to pass the stack size, parameters which is best using a csv type of string, in order to send more parameters as one parameter, and then the thread ID is returned out.

Creating Threads - Win32 apps | Microsoft Learn
CreateThread function (processthreadsapi.h) - Win32 apps | Microsoft Learn
ResumeThread function (processthreadsapi.h) - Win32 apps | Microsoft Learn

Check out the clarion help page “Launching a thread - behind the scenes”

If you want to see more of the internals, rohitab api monitor will show you whats being passed to the windows api’s.

Free Software and Source Code | Feed Your Brain | rohitab.com

Hello @Mark_Sarson
thank you for your answer. Yes, I’m using stack as the parameter (otherwise it won’t compile). It was c/p of ChatGPT example :slight_smile:
I’m using C11.1 and understand your point regarding execution order, it is not so important because each procedure call will return they result (related to userId) and that’s important.
I also understand your point regarding NOTIFY but what if user A start a procedure and it take few minutes to finish… all other users need to wait until procedure send a NOTIFY to parent so other users can proceed.
I thought that start-ing a procedure should solve this where each user can start a procedure and wait for answer… otherwise, what is the point?

Hello @RchdR
I pass a stack parameter like 25000 or 35000 but it still works the same. Problem is not the execution order (like Mark explained, it depends of OS) but problem is execution itself - sometimes, both procedure window are active (I put timer using 0{prop:text} = clock()), sometimes is active a window for one user (random) etc.

Hi @Daniel1,

After thinking more about it, I wonder if the tight loop in your procedure might be contributing to the issue. Instead of running a long loop, a more efficient approach could be using a timer event to break up the work into smaller chunks. This way, you can let each thread process part of the work and keep the UI and other threads responsive.

In fact, the Clarion Process Template seen in the likes of reports uses this technique. It processes records incrementally by responding to timer events, which prevents the UI from locking up and allows other threads to run smoothly. You might be able to adapt a similar approach for your procedure to handle each user’s task in smaller steps rather than all at once in a loop.

This could help ensure that threads are processed concurrently without one blocking the others.

I hope this helps.

Best,
Mark

So you are STARTing two threads from a procedure with a window which has an Accept() loop?

If Yes, the Clarion docs suggest this would work for starting two procedures.

IF MyConditionToStartTwoThreads = True
If Loc:LastStartProc <= 0 OR Loc:LastStartProc = 2
Loc:ProcAThreadID = Start(MyProcA,50000,MyCsvParamString) !Loc:ProcAThreadID is if you need the thread id.
Loc:LastStartProc = 1
Cycle !This then lets the accept loop and RTL do its stuff to resume Loc:ProcAThreadID
End

If Loc:LastStartProc = 1
Loc:ProcBThreadID = Start(MyProcB,50000,MyCsvParamString)
Loc:LastStartProc = 2
MyConditionToStartTwoThreads = False
Cycle !This then lets the accept loop and RTL do its stuff to resume Loc:ProcBThreadID
End
End !IF MyConditionToStartTwoThreads = True

Technically this is an alternative way to write the code but this example will increase your chances of lockups.
Loc:ProcAThreadID = Start(MyProcA,50000,MyCsvParamString)
Resume(Loc:ProcAThreadID)
Yield !Useful only if the MyProcA has no window as it hands back to Windows but doesnt help the Clarion RTL when a Window needs to be opened.
Loc:ProcBThreadID = Start(MyProcB,50000,MyCsvParamString)
Resume(Loc:ProcBThreadID)
Yield !Useful only if the MyProcB has no window as it hands back to Windows but doesnt help the Clarion RTL when a Window needs to be opened.

If the Loc:ProcAThreadID and Loc:ProcBThreadID is not required, technically the code can be shortened to
Resume(Start(MyProcA,50000,MyCsvParamString))
Yield !Useful only if the MyProcA has no window as it hands back to Windows but doesnt help the Clarion RTL when a Window needs to be opened.
Resume(Start(MyProcB,50000,MyCsvParamString))
Yield !Useful only if the MyProcB has no window as it hands back to Windows but doesnt help the Clarion RTL when a Window needs to be opened.

but this example code can also cause lockups.

The point is, you need the calling parent window to cycle through its Accept loop to resume the 1st or 2nd thread, in order for the Clarion runtime to be “happy” and lets Windows have some time to do its stuff in order to minimise lockups.

Its like @mark_sarson says, lockups can occur when starting too many threads too quickly, and this problem is also machine dependent because each machine can have fast or slow hardware and different apps running, so you might see this problem on low spec laptops, but not a xeon cpu rack server for example.

I’m assuming the two threads which need to be started, have their own windows? If yes, you should use rohitab to look at the amount of work that goes into 1) opening a new window, 2) reading the ini file and 3) opening any files. #2 & #3 are very slow but those three bits of a procedure are very intensive, and thats where your locks up will occur. Windows is very busy doing those three bits and MDI windows are slower more intensive for windows to open than a non-MDI aka SDI window.

I’ve specified 50,000 because ram is cheap and abundant now a days, unlike say the 8088 days.
Stack overflow - Wikipedia

TopSpeed / Clarion / JPI x86 calling conventions - Wikipedia

You might have better results if you start those 2 threads ahead of time then NOTIFY() them when it’s time to do the actual work.

Additionally, like Mark says, a timer would work better than a tight loop.

Thank you all for your ideas, maybe I was not a clear. The idea is to start a Procedure once user access. I Start-ed 2 procedures (one after another) just because I wanted to see what will happen (and in case if 2 users come almost in the same time).
Now I try this:

  resume(start(MyProcedure, 35000, 1,2))
  message('Click to proceed!')
  resume(start(MyProcedure, 35000, 2,3))

and here is the result (see below).
Second thread is started after I clicked message OK but I expected to to see second procedure running the code immediately after it started. Unfortunately, it is started after first thread is done.

loop4

So correct me if I’m wrong.

Caption window = 1st Thread
UserID: 1 waitingTime 2 loop: xyz thread 2 time 16:09.00 = 2nd Thread.

Do you want 1st thread Caption window below the 2nd thread UserID: 2 window, ie a different z-order or something else?

Window Features Z Order - Win32 apps | Microsoft Learn

Is this MDI window?

It is important to read the Help on START and RESUME. Things don’t usually run sequentially, what you see feels like the movie Pulp Fiction or Memento.

Code execution in the launching thread immediately continues with the next statement following the START and continues until an ACCEPT statement executes. Once the launching thread executes ACCEPT, the launched procedure begins executing its code in its new thread, retaining control until it executes an ACCEPT.

A must read from the C6 thread model change PDF:
Launching a thread - behind the scenes

1 Like

@RchdR I need this:
User A accesses the program and MyProcedure starts like this:
START(MyProcedure, 35000, User ID, someCsv)
(userID is generated as unique and is not relevant here - it is generated based on user login etc.)
At (almost) the same time, user B also accesses the program. At that moment, the system generates a new userId and some Csv parameter (related to that user) and starts the procedure as before
START(MyProcedure, 35000, User ID, someCsv)
but “userId” and “someCsv” have other values.
The point is that the procedure started for user A is still running, and in the meantime the system started the procedure with a new thread.
What I need is for the code running under MyProcedure (based on the parameters) to run independently so that both users get the results immediately after the code finishes.
Currently, system start procedure for both “users” but execution for second “user” is starting after first one is finished.

@CarlBarnes Thank you, I checked this but can not figure out how to solve it.

OK I solved it when I placed execution code on Window.TakeEvent procedure, before ParentCall.
Now procedure execute the code for each thread in the same time.

loop5

Now I have another problem:
for instance, if 6 procedures are running and procedures with thread 4 and 5 are finished, they are not closed until procedure which is called after them is closed.
I try with
POST(EVENT:CloseDown, glo:thread) or Post(event:close window…
also with this
RETURN LEVEL:FATAL
but it just sit there and don’t move :frowning:
I’m starting it with
glo:thread = start(MyProcedure, 35000, 1,random(1,5))
RESUME(glo:thread)

Any idea?

image

just curious if you put a yield() at the top (or bottom) of the loop does that make any difference? Might be worth a try…

 loop L = 1 to (wait time * 100)
   yield()
   ! some database process...
   0{prop:text} = 'userID: '&userID&' Time: '&waitingTime&' L Value: '&L
 end

Have you tried
POST(EVENT:CloseWindow, glo:thread)

In other words, treating the Control parameter as not optional?

Are you sure the window on the thread is receiving the event:CloseWindow event? Could you trap it and pop up a msg to say the event has been received?

Alternatively set a break point in the debugger for the window to trap the event:Closewindow and then watch what happens when the event:Closewindow is received in case something else is stopping the window from not closing properly.

@vitesse Thanks, yield() it does not helped
@RchdR Yes, I also try with POST(EVENT:CloseDown, glo:thread) and Post(event:close window, GLO:thread), it does not work.
Attached is example I created. Just compile and click on button, you will see what I mean.
Procedure with cycle 1 should close but it stays open
test.zip (324.0 KB)

image

Hi @Daniel1

Threads.zip (324.2 KB)

I have modified your code to make it work based on what I was talking about yesterday.

Having a tight loop is not ideal, and the code for data processing is now handled in a timer event.

You will also see I have used NOTIFY in the OpenWindow Event to notify the main thread to start another thread.

I hope the code is self explanatory.

Mark

2 Likes

Hello @Mark_Sarson , thanks for the code, now I get the point.
Thanks again.