Posts: 171
Threads: 17
Likes Received: 53 in 42 posts
Likes Given: 59
Joined: Oct 2023
Reputation:
3
I think that it is interesting that with the wonderful LitDev Extension that there isn't at least one function for threading. Maybe, I missed it if there is, but I looked and I haven't found anything. LitDev did write an excellent article about threading and how to avoid its pitfalls. I suggest a new function for the LitDev extension called (LDThreading). What it would consist of I'm not sure, but I am sure the best person to create it would be LitDev.
JR
Posts: 433
Threads: 15
Likes Received: 145 in 122 posts
Likes Given: 232
Joined: Sep 2023
Reputation:
6
01-09-2025, 07:12 PM
(This post was last modified: 01-09-2025, 07:13 PM by AbsoluteBeginner.)
Hello, jrmrhrb00
Maybe you are looking for this:
LDCall.CallAsync(dll, extension, obj, method, arguments)
Call any extension method asynchronously.
See example LDCallAsync.
Posts: 498
Threads: 38
Likes Received: 405 in 278 posts
Likes Given: 194
Joined: Aug 2023
Reputation:
18
01-10-2025, 11:40 AM
(This post was last modified: 01-10-2025, 12:10 PM by litdev.)
Hi,
Threading in Small Basic is tricky, mainly because all variables are global scope, i.e. variables are the same everywhere and if 2 or more threads are running at the same time changing the same variable chaos will ensue. For example a loop counter "i" used on one thread could be overwritten mid loop by code running on a different thread also using a variablle called "i".
Multi-threading is used for several purposes:
1] To handle asyncronous events that can occur at any time - SB does this (all events are handled on asynchronous non-UI thread), with the limitations that it is best to perform the action initiated by the event in a 'game loop' running on the main UI (User Interface) thread.
2] To speed long calculations that are independenent (the results of one are not needed for another). Usually this would mean running the same code on multiple threads - but in SB with variables having global scope this is not practical.
3] To perform slow calculation while leaving the main UI thread active. If a slow calculation is performed on the UI-thread, then the UI appears unresponsive - we have all seen this. So if a program needs some time consuming initialisation (e.g. downloading lots of resources) then this can actually be done in SB using a Timer which fires events asynchronously and do the downloading in the timer event.
So we can do most things apart from point 2, but we must also remember that the Primitive data type and SB arrays are inherently quite slow, so parallel calculations isn't a magic wand, better to carefully write the SB code more optimally, maybe using existing extension features (e.g. LDList) or write your own extension for an especially performance critical piece of code or move to another language if performance is critical (SB will never be fast!).
Example of LDCall.CallAsync HPMF426.000-0 - uses LitDev.dll and LDBasic.dll extension, note the different variable names in DoWork1, DoWork2, DoWork3 subroutines
If there are use cases I have missed or want more details on above then please reply.
Posts: 171
Threads: 17
Likes Received: 53 in 42 posts
Likes Given: 59
Joined: Oct 2023
Reputation:
3
01-10-2025, 06:58 PM
(This post was last modified: 01-10-2025, 09:05 PM by jrmrhrb00.
Edit Reason: Update
)
LitDev,
If I put the following in after Dowork1, Dowork2, and Dowork3: Thread=ZSUtilities.GetCurrentThreadId() and TW.WriteLine(Thread). Then I can see the threads that the work is done on which to me is just cycles through the loops. These should be running independently. I don't see that much difference in the totals. I do see the difference if it uses 1000,2000, or 3000.
An interesting thing is the threads are always 7 5 6. for the Dowork2 3000 thread 5 is really Fast 4011954185880. If you could actually select the thread to run on this would be the one.
I think there should be an object that is called LDThreading. Having a LDCall to some function that runs asynchronously doesn't mean that much to me. Maybe it would help my understanding better if you could explain the program and what you are trying to show.
Really good write up in your post!
JR
Posts: 336
Threads: 37
Likes Received: 122 in 96 posts
Likes Given: 227
Joined: Dec 2023
Reputation:
7
01-11-2025, 02:33 AM
(This post was last modified: 01-11-2025, 02:33 AM by z-s.)
asynchronously and multi threading could be found in ZSTask and ZSThreading respectively and extension size is approx 100 to 200kb so you could use with Litdev extension in your project if needed I can provide a detailed guide on using it with samples.
ZS
Posts: 498
Threads: 38
Likes Received: 405 in 278 posts
Likes Given: 194
Joined: Aug 2023
Reputation:
18
01-11-2025, 10:30 AM
(This post was last modified: 01-11-2025, 10:37 AM by litdev.)
Hi,
Some comments:
1] If you add Thread=ZSUtilities.GetCurrentThreadId() inside the DoWork subroutines you should use different variable names (Thread1 etc) in each subroutine. It is unlikely, but not impossible that they could be used at the same time, so its just good practice to do this.
2] The main purpose of this example is to show the syntax. Note for example the difference in how DoWork1,2 are called compared to DoWork3. num1, num2 and num3 are just used to give each othe subroutines more work to do, the double loops in the DoWork subroutines are there just to take some time.
3] I don't think there can be a systematic reason for one thread number to be faster than another. It is up to the OS how to allocate CPU time for different threads and can vary for many different reasons. It actually takes some time to set up the threads so often there is no speed advantage unless the actual work in the thread takes significant time. If I set num1=num2=num3, then they all finish about the same time, although DoWork1 is usually first (it starts first) and DoWork3 usually beats DoWork2 and sometimes DoWork1, maybe due to it being called directly rather than indirectly throuugh LDCall.Function which has a small overhead. I guess you could set up careful timings for this, but all I think you will see is small variation in how the OS is setting things up and probably no obvious reasons - could depend on loads of other things going on - hundreds of threads starting and finishing all the time.
4] To find what may be available (e.g. that LDCall.CallAsync exists) then either use this forum to ask as you did, or using SBPrime Tools->Search Extensions. For me, searching for "thread" gives stuff in FCExtensions, LDPhysics, ZSEnvironment, ZSThread, ZSUtilities. Searching for "async" also gives LDCall, LDftp, LDUtilities, LDWaveForm. A few searches with some likely keywords usually turns up whatever there may be.
Is there something in the sample program that you are unsure about, or some code you have that you are trying to do asynchronously (in parallel using threads). Is there a specific objective you have using threads.
Posts: 171
Threads: 17
Likes Received: 53 in 42 posts
Likes Given: 59
Joined: Oct 2023
Reputation:
3
LitDev,
I used your suggestion for identifying the 3 threads. What is the reason for the Flickr images? I can see that they get loaded, and the program shows them in the graphics window. Most of the time the 3 dowork's get posted before the images show being loaded. Is the purpose, to show work that is being done on another asynchronous thread?
It looks like you only call Dowork1 one time, but it displays 3 sets of numbers. How does that work? The same for Dowork2 and Dowork3.
JR
Posts: 498
Threads: 38
Likes Received: 405 in 278 posts
Likes Given: 194
Joined: Aug 2023
Reputation:
18
01-12-2025, 08:15 AM
(This post was last modified: 01-12-2025, 08:25 AM by litdev.)
The point of the Flickr is just showing something else using threads and how the CallComplete can be used, no deep significance. If you make num1..3 bigger then DoWork will take longer and the Flickr may finish first.
We call DoWork1..3 (3 of them) and each writes out following when they complete (+ anything you add in the DoWork subroutines). It's maybe not the most sensible output, but is shows the state of all 3 DoWorks as each one finishes, i.e. that the longer lasting jobs are part way through when the the shorter jobs finish.
Code: TextWindow.WriteLine("Total = "+total1+" : "+total2+" : "+total3)
TextWindow.WriteLine("Count = "+num1+" : "+num2+" : "+num3)
Posts: 171
Threads: 17
Likes Received: 53 in 42 posts
Likes Given: 59
Joined: Oct 2023
Reputation:
3
01-12-2025, 02:32 PM
(This post was last modified: 01-12-2025, 09:02 PM by jrmrhrb00.)
LitDev,
Here's output of the text window a little modified from your original. Thread1 is from dowork1. So, it looks like Dowork1 is ran 3 times. In your program I only see Dowork1 called one time! So, how is it called the other 2 times? I don't see it, but it must be or why would you print Total and Count 3 times? I finally got it. The reason is there are 3 completions. First 1000, then 2000, and then 3000. Then it looks like 3000 for the last one is a smaller number, but it's actually larger. It has one more digit!
PENDING
Started
Thread2 6
Thread3 7
Thread1 5
Total = 250500250000 : 229812886499 : 259811375700
Count = 1000 : 2000 : 3000
Total = 250500250000 : 4004001000000 : 4319658391125
Count = 1000 : 2000 : 3000
1
2
3
Total = 250500250000 : 4004001000000 : 20263502250000
Count = 1000 : 2000 : 3000
Here's your program back: MHMJ115.000 Slightly modified!
JR
Posts: 498
Threads: 38
Likes Received: 405 in 278 posts
Likes Given: 194
Joined: Aug 2023
Reputation:
18
01-12-2025, 10:48 PM
(This post was last modified: 01-13-2025, 12:58 PM by litdev.)
This is the bit where DoWork1, DoWork2 and DoWork3 are called async (in parallel on different threads), see 4th argument of CallAsync command.
Code: LDCall.CallAsync("litdev.dll","litdev","ldcall","function","1=DoWork1;2=1000;")
LDCall.CallAsync("LitDev.dll","LitDev","LDCall","Function","1=DoWork2;2=2000;")
TextWindow.WriteLine(LDCall.CallAsync("","","","DoWork3",""))
In this case, DoWork1 has num1=1000, DoWork2 has num2=2000 and DoWork3 has num3=3000.
So DoWork1 is fastest, and DoWork3 slowest.
As DoWork1 finishes, the current state is that total1 = 250500250000 (just finished), and total2 and total3 have made similar progress, but not completed.
As DoWork2 finishes, the current state is that total1 = 250500250000 (unchanged), total2 = 4004001000000 (just finished), and total3 still going.
Finally, as DoWork3 finishes, all 3 totals are completed.
|