People seem to like to use threads for the wrong reasons. Here are the scenarios under which I consider using threads to be acceptable. The first two apply to applications whose user interface code would normally run in the same thread as the application logic.
Notes on each:
If you’re hogging the CPU, e.g. filtering an image, you might wish to allow the user to continue working while the operation is in progress.
It’s possible to filter an image in parts, but it’s difficult to work out how much data should be processed at once before the GUI is allowed to update. A loop can check how many milliseconds it has been running for - and stop once it reaches some limit - but this will cause the loop to run slower.
Using a separate, perhaps low priority, thread (or threads) for such operations will not only allow the OS to handle multitasking, making your UI feel less sluggish, but also allow multiple CPUs (or CPU cores) to be used, which could give a significant speed boost.
If your system does not provide async I/O operations, any I/O operation may block. Even writing a small file to a local disk can block for seconds if the disk is currently busy. On most systems such a problem is rare and it’s not vital to allow the user to continue operating the user interface for this short time, so it could be acceptable to simply show a ‘busy cursor’ or provide some other feedback.
In s.q.u.e.l.c.h, a background thread reads metadata from Ogg Vorbis files while the user operates the user interface. To begin with, I had the metadata reading code in the same thread as the user interface, but, even though reading each file usually takes only a few milliseconds, this was enough to make the user interface feel sluggish.
Performing processing in small chunks requires storing state. Most ‘asynchronous’ code uses a ‘state machine’ design. Unfortunately, this also increases code complexity and makes it more difficult to quickly read the code and understand what it is doing. Using separate threads can allow you to make your code linear - though complexity re-appears in the form of the requirement to synchronise threads. Debugging can also become more difficult.