This article covers the basics of threading in Small Basic. This is usually quite an advanced concept and perhaps a surprising discussion point for Small Basic, but it does raise its head in many Small Basic programs. Therefore, it is worth explaining
what it is and how and why to avoid some common pitfalls.
A thread is a process within a program that can run at the same time as other processes or threads. This is called asynchronous since multiple threads can be running at the same time.
The threads may run on separate CPUs if you have a multi-core PC or be shared on one CPU. The operating system will take care of this for you, but you can always think of them running at the same time.
The main thread which is started when a Small Basic program starts and is called the UI (User Interface) thread. It is responsible for running the main program (everything apart from event subroutines). It is also responsible for updating what you see
(the UI), for example the
GraphicsWindow. When you call a command like Shapes.AddEllipse(20,20), the
ellipse object and its properties are created and a message is passed to the UI thread to update what you see on the screen.
When an event is defined in Small Basic, the event is associated with an event subroutine. The event subroutine is called on a new thread when the event occurs. Clearly an event like a mouse click can occur at any time and the event subroutine is called
asynchronously - in parallel or at the same time as the main program or even another event
The program always starts on the main thread, and additional threads are created in Small Basic when event subroutines are called. When an event subroutine exits, then the event thread is disposed of. There is therefore a big distinction between code that
operates on the main thread and code that is called from events.
It is not the actual subroutines that determine which thread they are on,
rather is is how they are called (from the main program or from an event). Therefore, it is possible to call an event subroutine (it is just a subroutine) from the main thread and also call a non event subroutine from an event.
In the code above, OnMouseDown is an event subroutine and RingBell is a 'normal' subroutine.
RingBell is called at the start on the main thread and it is then called by
OnMouseDown on the event thread each time the mouse button is pressed. So whether
Sound.PlayBellRing() is called on the main or the event thread depends on how it
This section lists some of the main issues with threads which are present in all multi-threaded programs and in particular in Small Basic. We also draw some conclusions about 'best practice' arising from these issues.
Because threading related problems can cause unpredictable behavior, debugging and fixing them can be especially difficult, so it is best to design your code with these ideas in mind.
All Small Basic variables are global scope. This means that they can be accessed and changed at any point within a Small Basic program. As a result, if different threads are using the same variable to do different things at the same time, then unpredictable
results may occur. Be particularly careful not to use the same loop index counters in the main code and event subroutines.
'Main UI thread
'Timer event thread
Above is a rather contrived example, but it shows the issue that the variable 'i' is used in the UI and Timer threads and therefore its value is changed by both giving unpredictable
results such as those below.
Conclusion - Don't use the same variable names inside code that can be called on different threads unless you want them to be modified and used by both, in which case there is no guarantee exactly when the
variables will change. In Small Basic variables are 'thread safe' so your program won't usually crash, but you may get unexpected behavior.
Re-entrancy is where an event thread is called again before it has completed the last event and exited from the event subroutine. Most Small Basic events are not
re-entrant. For example the following calls the bell each time the mouse is moved and doesn't recall the event until it has finished with the last. See that the debugging
TextWindow output shows
we Enter and Leave the event subroutine in order (not re-entrant). Note that when the mouse moves several events are raised in succession as the mouse moves, but only the last is queued for the event subroutine, resulting one extra bell ring after we
have finished moving the mouse.
Note however, using the Timer, this is
re-entrant with several entries before the first is finished.
If we stop the Timer after 2 calls, we see that many more bell rings (around 20) are
made before the bell ringing actually stops. In this case the Timer event calls are queued to be called before the timer is paused.
Conclusion - Re-entrancy is complicated and can be unpredictable. Do not do so much in the event code that it is likely to take longer to perform than the frequency at which the event may be called. The
best approach is to just set a flag
inside the event subroutine and 'do the work' on the main thread.
Below is an example of this
for the Timer event and the results are more predictable.
As stated above, the main thread also updates the UI. If we do a lot of update work in an event thread that takes some time, the UI is not updated until the event subroutine finishes.
In the example above the ball is not drawn until the event subroutine is finished. While the subroutine is not
re-entrant (see the TextWindow Enter and Leave debugging), the mouse move events are queued and continue to be called long after we have finished moving the mouse. This is
because the UI is updated from the last ball created and the new mouse move may change something so it is queued for calling.
If we delete the bell ringing (added to slow the event subroutine) we actually get a crash and the TextWindow debugging suggests that there may be some re-entrancy, perhaps the ball variable is being replaced before it is finished with. There are no easy
solutions here if we do the graphics (UI work) inside the event subroutine.
Conclusion - Don't do UI work inside an event subroutine, do it on the main UI thread, as shown below.
Note that creating hundreds of ball shapes with each mouse move is a bad idea, it was just done to create short examples.
As an advanced topic we can actually use the Timer thread to deliberately do something that takes a long time, while also doing normal stuff on the main UI thread. Bearing in mind the conclusions above:
As an example, I use a Timer thread to load images for use in a scrolling image viewer - I posted this as a solution to monthly challenge in the light of some discussions on threading and the Timer object in the forum. This was also the main reason to write
this article. Small Basic import code WFD474.
'Example solution to December 2013 Challenge (Curriculum 2.6(3))
'Uses Timer and Stack to
asynchronously download images in preparation for use
'Shape images are created and destroyed as required
'The only limiting feature is that ImageList will continue to grow gradually
'Setup variables and GW
'Start Timer to get images
asynchronously (in parallel
on Timer thread)
'Pause up to 10ms depending on time spent doing updates
'Current image number
'Store for current moving images
'Tag for Flickr image
'Asynchronous download of images - careful not to use any variable names used in main loop
' Stop the timer as this sub will continue
for ever loading images as required
'load up to 10 images ready to use
'If we don't use
ImageList then we cannot get the image size
'There is no way in standard SB to remove used images from ImageList so memory will gradually grow
'We can get some flicker when
image is added - to
minimise we immediately move off-screen
'Create first image
cpu when nothing to do
'Get the next image and add it to the moving list array
'Move the current active images
'Get current moving image and update its position
'Start a new image (with a pixel space of 10)
'Clean up and remove finished images
'Remove finished with shape