ExitThread() command

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

ExitThread() command

Post by BarryG »

We all know we're not supposed to use KillThread() to "stop" a running thread, but I have times when I need to cleanly exit a thread before its normal exit - like a virtual ProcedureReturn command that the thread can receive at any time from another part of its parent app. Could a command be added to do that? Thanks.

Note 1: I'm aware that exiting a thread cleanly requires cleaning up, like closing any files it opened, etc. My app only does calculations, so it should be safe to exit like this - and besides, an ExitThread() command could be smart enough to know what's been opened/created anyway and just close/free whatever automatically when "ExitThread()" is received.

Note 2: No, I can't do this with a global "exit" variable for the thread because my app creates LOTS of threads on-the-fly (not at app startup) with lots of actions that the user has specified during runtime, so there's literally (a) no way for the thread to know what the global "exit" var for itself it would be, and (b) no way to put a check between each action of that thread for that global var because the thread doesn't know how many actions it will be executing.

So if a command like this is feasible and easy to add, it would be great!
User avatar
STARGÅTE
Addict
Addict
Posts: 2085
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: ExitThread() command

Post by STARGÅTE »

BarryG wrote:My app only does calculations, so it should be safe to exit like this
If you don't need to "clean up" ressources like files, images, memories, ... then you can use KillThread(). Edit: Of cause, the "normal way" with an exit value has to be preferred.
BarryG wrote:and besides, an ExitThread() command could be smart enough to know what's been opened/created anyway and just close/free whatever automatically when "ExitThread()" is received.
No this is not possible. For example, some times you create images in a thread, draw somethink on it and push it to the main thread for using. Here, the ExitThread()-command should not free these images, which were created in the thread. On the other hand, some times you create images in a thread just temporary. Here, the ExitThread()-command have to free such an image. But how should ExitThread()-Command decide, which way is right? It is the task of the programer, to do all necessary clean ups.
BarryG wrote:Note 2: No, I can't do this with a global "exit" variable for the thread because my app creates LOTS of threads on-the-fly (not at app startup) with lots of actions that the user has specified during runtime, so there's literally (a) no way for the thread to know what the global "exit" var for itself it would be, and (b) no way to put a check between each action of that thread for that global var because the thread doesn't know how many actions it will be executing.
I did not understand your arguments. If you define a global variable "exit", then every thread can read this value and can stop its calculations or loops. Further, if you want to quit a thread with your "ExitThread()" procedure, you have to know the ThreadID, which have to stored somewhere. At the same place you also store typical objects like Mutex, Semaphores and so on, used in this thread, and also a private "exit" value. After you set this exit value, you have to use WaitThread() for all threads, to wait until they are done.

Btw: Also a "ProcedureReturn" is not in general a "cleanly exit". You have to free images, files, memories by yourself, otherwise you have even for procedures strong memory leaks.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
Saki
Addict
Addict
Posts: 830
Joined: Sun Apr 05, 2020 11:28 am
Location: Pandora

Re: ExitThread() command

Post by Saki »

Imagine you are working in your garden.
Then another helper comes along.
You can now both work in the garden, you can also use the same tool.
But you can't use the hedge trimmer at the same time.
You can also say, I'll use the hedge trimmer, you won't get it today.
When the helper is done, he cleans up and goes home.
If he doesn't clean up, there's trouble. :wink:

A peculiarity of these helpers is that they are very hard-working, but also very stupid.
And they are never smarter than their employer.
Also, they always have to be paid.

A useless or wrongly employed helper can do more harm than good. :o
地球上の平和
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: ExitThread() command

Post by BarryG »

Here's a (rough) example of what I mean. I have a lot of on-the-fly calcs that are done with different on-the-fly threads like this:

Code: Select all

Procedure CalcThread1(params)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
EndProcedure
That's one type. Another thread created on-the-fly (totally different calcs and code, but shown similar here for the example) might look like this:

Code: Select all

Procedure CalcThread2(params)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
  a-1 : Delay(delay_due_to_time_of_calc)
EndProcedure
Now, assume the delay is 1000 ms for each thread, due to the time it takes for each calc line to finish. As you can see, both threads would take 10 secs to complete.

But, assume I want to cancel the second thread (CalcThread2) for some reason (it happens). Using a global "cancel" var that I set to "1" (to cancel), my code now has to look like this to accommodate the cancel:

Code: Select all

Global cancel2

Procedure CalcThread2(params)
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
  If cancel2 : ProcedureReturn : EndIf
  a-1 : Delay(delay_due_to_time_of_calc)
EndProcedure
See how disgusting that is? Because I need to be able to cancel at any part of the thread, so I need to If/EndIf after every command in the thread in case "cancel2=1". But there could be >100 commands in each thread! I can't code that for every thread.

Now, what about CalcThread1? What if I need to cancel that, too? I need to set a new "cancel" var just for it, so it doesn't clash with the cancel var of CalcThread2. After all, I want CalcThread2 to keep running but stop CalcThread1. So a new global var is needed, but I don't know how many threads I'm going to have, so how will I know in advance how many global "cancel" vars I will need? See the problem? I can have the following, but it's not future-proof and there's no way of any given thread to know which global var is "theirs":

Code: Select all

Global cancel1, cancel2, cancel3, ...up to what? we don't know!
It's annoying. Currently I'm dealing with it just by using KillThread(), which 100% works and does the job but I know this isn't "clean", so I was hoping for an "ExitThread" that would simulate the "If cancel : ProcedureReturn : EndIf" bits because they're literally impossible to add manually.

This is my dream solution:

Code: Select all

Procedure CalcThread1(params)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
  a+1 : Delay(delay_due_to_time_of_calc)
EndProcedure

id1=CreateThread(@CalcThread1(),0)

ExitThread(id1) ; Done later when needed, instead of KillThread(), and it just exits the procedure at the next command (ie. the next calc in my example).
User avatar
NicTheQuick
Addict
Addict
Posts: 1226
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: ExitThread() command

Post by NicTheQuick »

If you do it right, it is not a big issue to create threads dynamically and have a "quit" variable for everyone of them. You even can get the return value of your thread if you want to:

Code: Select all

EnableExplicit

Prototype.i ThreadFunc(*parameter)

Structure ThreadInfo
	handle.i
	quit.i
	running.i
	function.ThreadFunc
	returnValue.i
	*parameter
EndStructure

Procedure ThreadFunc(*t.ThreadInfo)
	*t\running = #True
	*t\returnValue = *t\function(*t)
	*t\running = #False
EndProcedure

Procedure.i StartThread(function.ThreadFunc, *parameter)
	Protected *t.ThreadInfo = AllocateStructure(ThreadInfo)
	
	*t\quit = #False
	*t\parameter = *parameter
	*t\function = function
	*t\handle = CreateThread(@ThreadFunc(), *t)
	
	ProcedureReturn *t	
EndProcedure

Procedure QuitThread(*t.ThreadInfo)
	*t\quit = #True
EndProcedure

Procedure.i JoinThread(*t.ThreadInfo)
	*t\quit = #True
	
	While IsThread(*t\handle)
		WaitThread(*t\handle)
	Wend
	
	ProcedureReturn *t\returnValue
EndProcedure

;--------------------------------

Procedure.i CalcThread1(*t.ThreadInfo)
	Protected a
	
	While Not *t\quit
		a+1
		Delay(100)
	Wend
	
	ProcedureReturn a
EndProcedure

Procedure.i CalcThread2(*t.ThreadInfo)
	Protected a
	
	While Not *t\quit
		a+1
		Delay(150)
	Wend
	
	ProcedureReturn a
EndProcedure


Define thread1 = StartThread(@CalcThread1(), 123)
Define thread2 = StartThread(@CalcThread2(), 456)

Delay(5000)

Debug JoinThread(thread1)
Debug JoinThread(thread2)
But the issue with terminating the thread after every calculation step still persists.
Last edited by NicTheQuick on Wed Jan 06, 2021 2:19 pm, edited 1 time in total.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: ExitThread() command

Post by BarryG »

Hi Nic, I'm a bit drunk right now but that looks more confusing than just throwing the hammer of KillThread() at it... I will look again tomorrow when sober (if I remember).
User avatar
NicTheQuick
Addict
Addict
Posts: 1226
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: ExitThread() command

Post by NicTheQuick »

BarryG wrote:Hi Nic, I'm a bit drunk right now but that looks more confusing than just throwing the hammer of KillThread() at it... I will look again tomorrow when sober (if I remember).
You only have to focus on the stuff beyond this line

Code: Select all

;--------------------------------
Have fun.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: ExitThread() command

Post by BarryG »

But... see this:

Code: Select all

Procedure.i CalcThread1(*t.ThreadInfo)
You've got two hard-coded procedures to handle it. But I don't know how many threads I'll have... could be 1, could be 100, could be 1000. I can't code 1000 x "Procedure.i CalcThread1(*t.ThreadInfo)" for all scenarios. It's hard for me to explain. Just know that my app can create X amount of threads on-the-fly (when the user requests one), and I need to kill any of them when the user requests it.
User avatar
mk-soft
Always Here
Always Here
Posts: 5393
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: ExitThread() command

Post by mk-soft »

Write and read accesses from different threads to common variables must be protected with mutex

For remember my solution for threads ...
Mini Thread Control
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
NicTheQuick
Addict
Addict
Posts: 1226
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: ExitThread() command

Post by NicTheQuick »

Now I don't know what you talking about. If you need fundamentally different thread procedures you indeed have to write them.
If you think you need 100 or 1000 different procedures then you are thinking wrong.

Also you can not write procedures dynamically, except you have an runtime-assembler and you are allowed to execute code from memory. But that's a whole other level.

If you just want to run the same procedure 100x times concurently, then just do it.
Here's an example for 25 concurrent threads which all do the same thing.

Code: Select all

Procedure.i CalcThread(*t.ThreadInfo)
	Protected a
	
	While Not *t\quit
		a+1
		Delay(100)
	Wend
	
	ProcedureReturn a
EndProcedure


Dim *threads(24)
Define i
For i = 0 To 24
	*threads(i) = StartThread(@CalcThread(), i)
Next

Delay(5000)

For i = 0 To 24
	Debug JoinThread(*threads(i))
Next
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
BarryG
Addict
Addict
Posts: 3320
Joined: Thu Apr 18, 2019 8:17 am

Re: ExitThread() command

Post by BarryG »

NicTheQuick wrote:Dim *threads(24)
This is the problem, which I said at the start. I don't know how many threads are going to be running at any given time. What do I hard-code here? You said 24, should I put 9999 to cover all possibilities?

Anyway, let's forget workarounds and hope an ExitThread() command can be done as an alternative to KillThread(). That's all I really need.
User avatar
mk-soft
Always Here
Always Here
Posts: 5393
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: ExitThread() command

Post by mk-soft »

ExitThread(id) is not enough. There must also be a way to query the ExitThread status in the thread procedure in order to delete any resources before the thread is terminated. Otherwise you have the same as with KillThread.

So you can also programme it yourself. See Mini Thread Control
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
NicTheQuick
Addict
Addict
Posts: 1226
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: ExitThread() command

Post by NicTheQuick »

BarryG wrote:
NicTheQuick wrote:Dim *threads(24)
This is the problem, which I said at the start. I don't know how many threads are going to be running at any given time. What do I hard-code here? You said 24, should I put 9999 to cover all possibilities?

Anyway, let's forget workarounds and hope an ExitThread() command can be done as an alternative to KillThread(). That's all I really need.
Well, then use a LinkedList. That was just an example. I assumed you know how to use Arrays, LinkedLists and such things.
But keep in mind that you can not run infinitely many threads. Every system and operating system has some limits.

Maybe you should consider using thread pools. For this you create a bunch of threads at the start of your program that wait for tasks and then feed them their tasks from an other thread.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
infratec
Always Here
Always Here
Posts: 6869
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: ExitThread() command

Post by infratec »

As already said: your 'program flow' is wrong.
You should re- think about your current thread solution.

But (@NicTheQuick) a thread has no return value.
The result has to be placed in the parameter struct or it needs PostEvent().
User avatar
NicTheQuick
Addict
Addict
Posts: 1226
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: ExitThread() command

Post by NicTheQuick »

infratec wrote:As already said: your program flow is wrong.

But (@NicTheQuick) a thread has no return value.
The result has to be placed in the parameter struct or it needs PostEvent().
I created my example in a way so that a thread can have a return value. This is also quite common in other programming languages. There you usually have a function like "join" which waits until the thread terminates and returns the return value of that thread.
I used the structure ThreadInfo in my example above.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
Post Reply