TimersTimers appear in the list of built-in Controls when you're editing a Window in layout mode, but it is not a subclass of RectControl or Control. The fact is that all classes can be used as Controls in REALbasic as long as they are dragged to the Window while in Layout Mode. This means that they are implicitly instantiated when the Window is created and that any events that have been created in class can be edited in the same way that user interface Controls areby double-clicking them. The reason I'm emphasizing Timers not being Controls is because it means that you can instantiate Timers using the New operator. This, as it turns out, is really the biggest difference. This means you have a choice; you can instantiate a Timer like any other class, or you can drag it onto your Window. If you instantiate the Timer with New, it needs to instantiated as a property of an object that will persist, such as Window1, App, or a module because Timers do things periodically and to do so, they need to hang around for a while. Timers have Enabled properties like Controls do, and they can be part of Control Arrays. You can even Control where it is positioned on the Window, but don't worry because a Timer is invisible when the program is running. All the hard work is done using two propertiesan event and a method. Timer.Mode as Integer The Mode determines whether the Timer performs its task repeatedly, or just once. You can also turn it off. This is set using the following class constants: Timer.ModeOff = 0 Timer.ModeSingle = 1 Timer.ModeMultiple = 2 If the Timer is set to fire periodically, you also need to tell it how often to do its thing: Timer.Period as Integer The Period is measured in milliseconds. By default, it is set to 1,000 milliseconds. Any value less than 1 millisecond is treated like a millisecond. After you've decided how often a Timer is going to do something, you should probably decide what it's going to do as well, and that is done in the Action event: Timer.Action You can reset the Timer by called the Reset() method. To use a Timer, drag it to the Window. Double-click it and add code to the Action event. Timers can be used for a lot of things, but they are commonly used in tasks relating to the user interface. If you recall the Keyboard object, a Timer can be used to periodically test to see if a certain key is being pressed. Because of this relationship with the user interface, Timers will try to do their work according to the time established in Timer.Period, but they will wait if a lot of activity is going on. If I have a Timer set to do something every 5,000 milliseconds, but if the user is typing away in an EditField, the Timer won't trigger the Action event until there is a pause. This means you cannot count on a Timer to do its work like clockwork. Timers can also be used when working with asynchronous objects. Classes used in networking are good examples because there is always a degree of latency when interacting with another computer through a network. Your application may make a request and then wait for an answerthe Timer can periodically check until the data is available, and then act accordingly. Periodic Events with the Timer ControlApp.DoEventsEvents get called in sequencethey do not run concurrently (that's what threads are for). When the code in an event is executed, that's the only code that's running until it is finished doing its thing. If the code happens to involve a big loop, or takes a lot of time for some other reason, everything else has to wait, even user interaction with your application. This applies to the Timer.Action event, too. One way around this problem is to call App.DoEvents, which stops the processing of the current event so that other events can be triggered. You have to be careful when using it because it can cause problemsprimarily if you have one event call App.DoEvents, which then allows another event to execute and that event calls App.DoEvents, and so on. You can easily lock up the application altogether. ThreadsAlthough not technically controls, Threads are often used as controls and they are listed in the Controls List in the Window Layout Editor. In this example, I do not use it as a Control and I think it serves as a good example of the limitations of using Controls in certain circumstances. In this case, I want to have three identical Threads running concurrently. If I were to drag a Thread object onto the Window, I would have to have three versions of the same code. If I made a change to Thread1, I'd need to make a change to Thread2 and Thread3. The basic convenience of dragging a Thread to a Window is that you don't have to instantiate it yourself, but is it really that hard to instantiate it yourself? So I have three concurrent Threads all doing the same thing. They all share the same resource, which is Window1. They all call the same methods: Window1.getRequestCount and Window1.makeRequest. Each time a request is made, the requestCount property is incremented by one. The problem of controlling access to shared resources is one of the fundamental challenges associated with using Threads. This is something of an artificial example, but it does allow you to see fairly clearly just what kind of thing can go wrong. If I were running this method in a single Thread, or as a regular method in some other part of the program, the numbers displayed would increment evenly as it looped. However, when you run three identical Threads that are each accessing the same resourcenamely, the Window and the value of RequestCount, things get a little more confusing. On Window1, there is a ListBox, a ProgressBar, and an EditField for each Thread. There is also a CheckBox that is used to indicate whether you should use a Semaphore; you'll see what that does momentarily. The EditFields are used to set the priority for each Thread. The priority defaults to five, and it is calculated relative to the priority of other Threads. When we start off with Threads having equal priorities of five, it means that Thread1 gets 1/3 of the resources, Thread2 gets 1/3 of the resources, and so on. The steps that the Thread takes are these: it checks to see the value of the requestCount property of Window1. Then it enters a fairly lengthy loop, and it makes a request, which increments the requestCount property. It then populates the ListBox with the results. In the left column is the loop number (how many times this thread has looped). The second column shows the value of requestCount when the loop starts, and the third column shows the value of the requestCount after making a request. This means that the third column should contain a value that is equal to the second column, plus one. In fact, if just one Thread were running, that is exactly what we would see. In the first iteration, I will run the application with the Threads having equal priorities and without using a Semaphore. When you look at the results, one of the first things you should notice is that Thread1 goes through several loops before yielding to Thread2. That's important to understandThreads only appear to be running concurrently. In fact, Thread1 runs a little while, then Thread2, and so on. This is the very reason why you need to set the priority, so that you can manage this process. You'll also notice that it's a little hard to predict exactly when one Thread will yield to the next. The reason is that REALbasic uses cooperative threading, which means that instead of executing a specific number of statements in Thread1 and then a specific number of statements in Thread2, the application yields from one Thread to the next at times that make sense, such as the end of a loop. In this example, there is an inner loop that loops 500 times. Your Thread can yield during that loop just as easily as it can yield during the outerloop. The way that you can tell if this has happened is that you find that the value of requestCount, after you have looped and incremented, is more than one larger than the value of requestCount prior to the loop. The following screenshot shown in Figure 5.25 shows how the ListBoxes appear at the end of the loop cycle. As you can see, in almost every row, the start value and the stop value have a difference of much more than one. Now this is a relatively harmless example, because nothing gets broken. Imagine, however, what the consequences would be if all three Threads were writing to the same file at the same time. What would happen to the data? Whatever happens, the likelihood is that it would be a Very Bad Thing. Figure 5.25. Thread1.How do you keep this from happening, you ask? There are two approaches, both of which are minor variations of the other. There is a Semaphore class and a CriticalSection class, both of which can be used to limit access to a resource to one Thread at a time. A Semaphore can be used to control access to multiple resourcesthat's one of the parameters that you can set. What is unique about a CriticalSection is that the same Thread can get a lock on it more than once (which means that it has to exit the same number of times to clear the Thread). Now, check the Use Semaphores CheckBox and run the application again. You'll find upon inspection that with each loop, the value of requestCount when it is incremented after the loop is exactly one more than the value of requestCount before it entered the loop, which is exactly what you want. Now only one Thread can access the resource at the same time. Not that you would ever really want to do this, but you could also put the Semaphore or CriticalSection outside the main loop of the Thread and that would mean that each Thread would execute in sequence. Thread1 would loop 100 times, then Thread2 would loop 100 times, and so on. See Figure 5.26. Figure 5.26. Program output when using a semaphore.What you see is that Threads only make it appear that things are running concurrently. A certain number of statements will be executed in one Thread, and then a certain number of statements will be executed in the next, and so on. You get to decide how much time and resources are devoted to each of your threads. Threads can be complicated because you can have two or more Threads acting on the same set of data in an unpredictable way, so there are some instances where you have to take special precautions and ensure that only one Thread is running at a time. For example, you don't want to have two Threads writing to the same file at the same time. Here is the code used in the previous examples: Class Window1 Inherits WindowListing 5.58. Sub Window1.Open() Handles Event
Listing 5.59. Function Window1.makeRequest() as Integer
Listing 5.60. Function Window1.getRequestCount() as Integer
Listing 5.61. Window1 Properties
Listing 5.62. Sub Window1PushButton1.Action() Handles Event
Class ThreadTester Inherits ThreadEach Thread has a loop that loops 100 times. At the start of the loop, the Thread checks to see what the value of Window1.RequestCount is, and then it goes into a second loop whose only purpose is to take up some time. When that loop is finished, it checks the value of RequestCount again to see if it has changed. The number of the current iteration of the loop is displayed in the ListBox, as well as the value of RequestCount at the start of the loop iteration and the end of the loop iteration, after makeRequest as been called, which increments RequestCount by one. Listing 5.63. Sub Run() Handles Event
Using Threads for Background TasksThe RSSReader application will allow the user to subscribe to any number of RSS channels. Each of these channels needs to be updated periodically, and the time between updates is specified in the RSS file itself. You could wait to check to see if you need to download a new version of a document when the user requests it, but you can provide a better experience if your application looks for those files that are due for updating, then updates them in the background without interfering with the user's other activity. This means that when the user selects a channel to read, it will be available without any waiting. Class tWriter Inherits ThreadListing 5.64. Sub tWriter.Run() Handles Event
Listing 5.65. Sub tWriter.Write(toWrite as iWriteable)
Listing 5.66. tWriter Properties
|