Creating helper apps

There are times when you may want to take advantage of multiple CPU cores, something that you cannot do with Threads. With a multi-core CPU, your computer can literally do multiple things at one time, which is called multiprocessing. With a little careful planning, your Xojo apps can use multiprocessing for significant performance improvements.

You may be thinking, isn 't this what threads are for? Sometimes, although not with Xojo. As noted in the previous section, Threads in Xojo use co-operative threading, which is simpler and more efficient but means that all the threads run on a single CPU core.

The Worker class

If you are intending to create a helper app for data processing, the first thing you should do is check out the :doc:`Worker</api/language/worker>`class as it's designed to do just that and takes care of all the overhead of having a helper app for you.

Console app helpers

With Xojo, separate processes using console apps is the way to take advantage of multiple CPU cores. The technique is relatively simple. You create a console app that does the processing you need and returns a result. You then have your main (GUI) app start one or more of these console apps and supply them with the data they need to process. Each console app goes off on its own (in its own memory space and using its own CPU core -- if one is available) to do the processing. When one finishes, it returns the result, which your app can then use.

Communication techniques

There are two common ways for your main app to communicate with the console apps: Shell or IPCSocket.

Using a Shell is simplest. You create a Shell instance for each console app and launch the console app from the Shell. You can get the return result using the Shell methods and properties.

An IPCSocket is more useful when the console apps need to continuously communicate with the main app, but it is also a more advanced technique.

Word counter example

Imagine you are responsible for receiving documents to publish on the company newsletter and need to know the word counts. You used Xojo and created a quick app to add a bunch of documents to a window and go through and count all the words in each document, one by one.

The WordCounter example does this simple task of counting words in text files. The word count algorithm is not very speedy, but that is OK because the example also shows you how you use multiple CPU cores to speed it up.

The WordCounterGUI app displays the UI and can count the words. The WordCounter console app is a helper that is used to count the words using multiprocessing.

The example can count words using three different techniques:

  • Sequential: This simply counts the words in each file, one after the other in the main thread. This locks up the UI while the words are being counted.

  • Threaded: This creates a separate thread for each file and the words are counted using the thread. The UI is reponsive while the words are being counted.

  • Processes: This launches the WordCounter console app for each file and the words are counted by the console app. The UI is responsive while the words are being counted.

The four sample documents used to test the different techniques are chapters from A Princess of Mars by Edgar Rice Burroughs.

As you might expect, the sequential method is pretty slow, taking about 44 seconds on the test machine to count all the words in the four documents.

The thread method is slightly slower at 46 seconds. This may surprise you, but it shouldn 't. As covered in Running code in a thread, threads in Xojo are co-operative. This means they still run on just a single CPU core. Add in the extra overhead for managing the threads and you get a slightly slower run time. But the threaded version is much nicer to use. The word counts appear in the order they are completed (shorter files to longer files) and the ProgressWheel spins while it is counting words.

The processes method is fastest. On the test computer it finished in about 21 seconds. Four separate console apps were launched, one for each document, and each quickly took over its own CPU. As each console app finished, the results were returned back to the GUI and displayed.

Of course you will get different times on your system depending on the OS and number of CPU cores.

To try the app, run the built version for your OS platform in the WordCounterGUI Build folder. This folder has the console app set up properly for each platform. If you want to run WordCounterGUI directly from Xojo, you will need to make sure you copy the corresponding console app so that it is alongside the debug app (or change the code that finds the console app).

To use the app, click "Add File" to add one or more files (you can multi-select in the file selector dialog). You can then choose from Sequential, Threaded or Processes to choose the processing technique. Then press "Count Words" to count the words.

How does it work?

The WordCounterGUI app has four main project items: MainWindow, WordCounter, WordCountThread and WordCountShell.

MainWindow is the UI. It does not do any processing.

WordCounter is a simple class that take a file (in the constructor) and counts the words by calling the Words method. It is called by all three of the processing methods.

WordCountThread is a Thread subclass that uses WordCounter to count the words in a file. A separate instance of WordCountThread is created for each file added. MainWindow has a Timer to watch the progress of these threads so that the word counts can be updated in the ListBox and the elapsed time calculated.

WordCountShell is a Shell subclass (actually, an asynchronous shell) that starts a console app and waits for the word count result. A separate instance of WordCountShell is created for each file added. MainWindow has a Timer to watch the progress of these shells so that the word counts can be updated in the ListBox and the elapsed time calculated.

The console app is event simpler. It contains the same WordCounter class from above. In the Run event handler, it gets the file path (that was passed as a parameter) and sends it to a WordCounter instance to count the words in the file. The results are printed to the output and the console app quits.

Specifics about the processes technique

Look at the code in MainWindow.CountWordsInFilesUsingProcesses. The code is pretty simple: it loops through each file added to the ListBox and creates a new WordCountShell, passing in the file. Then the WordCountShell.CountWords method is called to count the words. When the console app outputs the word count, the DataAvailable event handler is called and the word count is saved in a property. The shell then stops running.

ShellTimer on MainWindow periodically checks to see if a shell has stopped running. If it has, then it gets its word count and updates the appropriate file in the ListBox.

WordCountShell.CountWords has the code that starts the console app using the shell and passes in the file name as a parameter. The shell runs asyncrhonously so that other processing can continue.

That is pretty much it. Overall it is a simple technique, but you can certainly make it more sophisticated. For example, an IPCSocket could be used in place of the shell for communicating with the console app. And this might make more sense if you want to have a fixed number of console apps continuously running in the background, waiting to be assigned work.