| |||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||
Print This Article REALbasic University: Column 105
OOP University: Part Twenty-SevenLast time I demonstrated how to create a reusable progress bar dialog. It makes it much easier to add a simple progress bar to any program, however, there are situations where the method used doesn't work. Today we'll explore some alternative approaches.
Timers and ThreadsFor our reusable progress dialog, I've chosen to use a global property, gPleaseStopProgress, to alert us to when the user has cancelled. I had to do this because the dialog itself can't know anything about the task being worked on and the task needs some way to know that it should be cancelled. This method, however, has some disadvantages and won't work in all situations. For one, it requires you add the global property to every project that uses the dialog. For another, your task must constantly check the global property's status to see whether or not it has been cancelled. For a simple loop task, that's fine, but for a complicated task that doesn't repeat, you'd have to interrupt your task's code with dozens of cancel-test checks. There are also certain events that don't seem to respond well to normal processing within REALbasic. For example, I receive this letter from Matt Eastburn who experienced a problem with his progress bars not updating during serial communications:
I did some experimenting with Matt's code and he's correct, at least to an extent. I didn't experience the same required mouse movement issue, but my reusable progress bar did not update during serial communication. It seems that for some reason the serial port monitoring blocks the cancel monitoring normally done during normal tasks. I ended up creating a workaround that involves testing for Command-period manually during the loop. Not the greatest technique, but it worked. (Of course for Matt's Windows machine he'd check for the Escape key, not Command-period.) If you're curious, here's how I changed Matt's code to work on my machine:
This is within the dataAvailable event of serialDriver (a serial control). There are other techniques that can also work in unusual situations. One approach is to use a timer control. Set it to fire every so often (perhaps every second) and when it runs, have it check to see if userCancelled is true. Unforunately, if your task is CPU intensive (a long calculation for instance), this won't work because timers have a low priority and just won't fire if the computer's too busy. A better approach is to spin your task off in a thread. Now threads tend to scare off people, but they're really easy to work with, though there are some gotchas and they can get complicated in certain circumstances. We've never covered threads on RBU before, so this is a good introduction. First, understand what a "thread" is: it's an independent task that runs on its own. Threads are what allows a single program, such as the Mac Finder, to do more than one thing at a time (for example, copy one set of files while still being responsive to other commands). Within REALbasic, normally when you start a long task, the whole program stops until that task is finished. If you put that task into a thread, the program doesn't stop. The user can still interact with the program. Of course any data or result generated by the task won't be available until the task is finished, but at least the user can continue to work with your program. Now here's where threads get complicated. Generally when you're writing your program, you expect things to happen in a linear fashion (i.e., step-by-step). The user clicks the "sort" button, you call your sort routine; the user clicks print, you call your print routine; etc. But threads change all that. If your sort routine is in a thread, your user could start the sort then print the data before it's sorted -- perhaps not what they wanted. There also can be interdependencies within your code. One routine might be in a thread, working on some task, when a second routine wants to work on the next part of that task but can't because the first isn't finished. For this kind of situation you must be sure every process in your program knows the previous process is finished before continuing (each process could set a "finished" property, for instance). Another problem is when two different routines need to work on the same data structure. If you have a database that's being sorted by one thread, what happens if another thread attempts to delete or add a record to that same data structure? The solution to this problem is anticipate it and create a system to "lock" the data (by setting a flag) so that other routines won't mess with the data until it's unlocked. REALbasic gives you an easy way to do this via the semaphore class. These kinds of issues with threads can be complicated, but we'll leave them for advanced users and won't get into them any further in today's tutorial. I just want you to understand some of the more complicated problems that threads can present. However, in most cases, thread usage is straightforward. For our purpose today, let's assume we've got a simple but time consuming task we need to run in the background but have it cancellable if the user desires. For an example, let's just use the single process bar code we used before. Except this time we'll run it from within a thread. First, we need to create a thread object. We do this by adding a new class to our project (File menu, "New Class"). Name the class longProcessThreadClass. Double-click on it to open it, and go to its run event. Put in this code:
This code is exactly like the code we used before: the only difference is that we now specify that the cancelCheck control is available on window1. Now drag longProcessThreadClass onto window1 to create an instance of it, and rename the instance longProcessThreadInstance for clarity's sake. Create a new pushButton on window1 with a caption of "Test single bar with thread" and inside its action event put this:
That's it! You can now run the test app and when you click the new button, it will run the thread. The program is still responsive to your actions, except we're displaying a modal dialog (the progress bar dialog) which prevents you from doing much. But the program will respond to a cancel command (albeit slowly). Note that within your thread code you still need to periodically check to see if the user has cancelled. But the thread system has the advantage that it will run without bringing all other aspects of your program to a complete halt. A thread would be ideal for use when you want a non-dialog progress bar (i.e., like the progress bar displayed at the bottom of the window in Internet Explorer or Safari or other web browsers to show page-loading progress). Above I mentioned that this method cancels "slowly" -- what I mean is that it doesn't respond the instant you press Command-Period, but if you hold down the keys, it will stop after a second or two. I believe this is because of a flaw in how userCancelled works (it must not poll the keyboard often enough or something). When I changed the code to add in a manual check using asyncCommandKey, the system worked instantly, but of course we run into the limitations I mentioned earlier regarding key codes. Here's how I modified the run code:
This is included in the today's project file, but it's commented out, so you can test the project both ways. Remember, you'll need to modify the check if you're on Windows or using a non-English keyboard. (For those of you confused by key codes, I've got an explanation of those coming in next week's Letters segment.) If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.
Next WeekLessons in debugging.
LettersNo letter today since the column focused on responding to one. About the Column REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers. Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.) About the Author See the REALbasic University Archives
REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.
| |||||||||||||||||||||||||||