Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual Basic 2005 Express Edition - From Novice To Professional (2006)

.pdf
Скачиваний:
387
Добавлен:
17.08.2013
Размер:
21.25 Mб
Скачать

480

C H A P T E R 1 7 T H E I N T E R N E T A N D V I S U A L B A S I C

The program seems trivial, doesn’t it? Think about what it’s doing for a second, though. We didn’t write any code to find out exchange rates; we called a method on a web service. That web service is hosted on a computer in England and takes care of all the dirty business of looking up financial data and calculating an exchange rate between any two currencies. When we make the method call, VB Express behind the scenes connects to a web server in England, locates the web service we specified, sends data to it, gets data back, parses it into the correct format, and returns the result to you, just as if you’d called any method on a typical class.

It’s also worth bearing in mind that we just used WebserviceX.NET here because it has a great selection of services and doesn’t require us to register or jump through any hoops to use it. But you may be interested to learn that Google, eBay, and Amazon.com all provide extensive web services of their own. You need to go to their websites and register to use them, but after you are registered you have the complete power of the Google, eBay, and Amazon systems at your disposal to embed in your own code. In fact, the Movie Starter Kit application that ships with VB Express does just that, using Amazon.com’s web services to locate movie information for you.

Summary

Visual Basic 2005 Express is far more than just a desktop development tool. Armed with the WebBrowser control, the classes in the System.Net namespace, and VB Express’s excellent support for consuming remote web services, there is nothing stopping you from writing awesome applications that reach out far beyond the desktop and combine live data from sources all over the world.

C H A P T E R 1 8

■ ■ ■

Threading

Icut my teeth programming games on the Commodore 64 back in the 80s. I remember vividly how cool it was when I discovered how to manage “interrupts” on that machine. The basic principle was simple: after a certain period of time, the computer had the ability to stop what it was doing and do something else, and then come back to what it was doing in the first place. This could happen hundreds or even thousands of times every second and give the impression that my humble 64 was doing two things at once. We’d use it to play a tune while at the same time keeping the graphics on-screen moving, and still responding to the player hammering a joystick.

Computers today still do the same thing. The difference between then and now is that modern chips from Intel and almost any operating system worth its salt are all designed from the ground up to do this. In fact, some of the newer dual-core and hyperthreading processors are designed to appear almost like two processors within the machine. Likewise, modern programming languages like VB make doing this kind of thing easy.

Microsoft Windows is designed to run more than one process (program) at once. In fact, even when it’s running just one program, it’s actually running a lot more than one. Just take a look at the Task Manager (press Ctrl+Alt+Del) to see a complete list of all the things running on your machine. These processes themselves are able to fire off small blocks of code to run concurrently with the main program, and these are called threads. What’s happening behind the scenes is that the processor stops running one program and switches to the next, millions of times a second. If you have more than one physical processor in the machine, each processor does the same thing, making it seem like the computer is phenomenally powerful.

Threading, the act of getting more than one “thread” of code running at the same time, is wonderfully easy in Visual Basic 2005 Express, and in this chapter you’ll see just how.

But before you dive into the code and see how to do it, maybe you’re wondering just why you’d want to. The most common reason is user interface responsiveness. If your user interface has to load up a massive amount of data when the form loads, the chances are that the computer would seem to “hang.” Windows may get to draw only a fraction of the user interface elements it needs before your code kicks in, chunking away so much that it prevents Windows from getting its own job done (making your user interface appear). In such instances, it’s not uncommon for developers to load all that data in a separate

thread. The user interface then comes into view very quickly, and the user is able to work

481

482 C H A P T E R 1 8 T H R E A D I N G

with it, all while out of sight and out of mind a separate thread loads up all the data it needs for the program to work properly.

Timers

The simplest introduction to the principles behind multithreading is the Timer control. It’s a dream to use, but technically it’s not true multithreading. The event raised by the timer actually runs on the same thread as the main application. To use it, you just drop the timer onto a form, tell it when to fire, and then code in an event to respond to the timer going off. Unlike a cooking timer though, the .NET timer fires over and over at the same interval until you turn it off. Let’s take a look.

Try It Out: The Timer Control

Start up a new Windows Forms application in Visual Basic 2005 Express. When the form editor appears, drop two Timer controls onto the form (you can find the Timer control on the Components tab of the Toolbox).

Select the first Timer control and take a look at its properties. Like many nonvisual controls, it doesn’t have a great many of them (Figure 18-1).

Figure 18-1. The Timer control has very few properties associated with it.

Only two properties are interesting: Interval and Enabled. The Interval property defines how often the timer fires its event, in thousandths of a second. So, setting a value of 500 causes the timer to fire every half a second. Go ahead and set the Name property to allNumbersTimer, and set the Interval property to

C H A P T E R 1 8 T H R E A D I N G

483

500. Set up the Name property of the second timer to evenNumbersTimer, and set its Interval to 800 (so it fires every 0.8 of a second).

Next, drop two lists and a button onto the form, as in Figure 18-2.

Figure 18-2. Drop two lists onto the form—our timers will populate these at runtime.

Name one of the list boxes allNumbers and name the other evenNumbers.

Now you’ll write code into the two timers to populate both list boxes. Double-click on the allNumbersTimer to drop into the Timer control’s Tick event. This is the event that fires every time the timer interval is reached. Just add a member variable into the class and fill in the Tick event so that it increments the variable and adds it to the list box, like this:

Public Class Form1

Private _num As Integer = 0

Private Sub allNumbersTimer_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles allNumbersTimer.Tick

_num += 1 allNumbers.Items.Add(_num)

End Sub

End Class

Remember to put the _num variable outside of the method to make it a member of the class, and not the method.

484

C H A P T E R 1 8 T H R E A D I N G

Now do something similar for the evenNumbersTimer control. The variable and code are shown in bold here:

Public Class Form1

Private _num As Integer = 0

Private _evenNumber As Integer = 0

Private Sub allNumbersTimer_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles allNumbersTimer.Tick

_num += 1 allNumbers.Items.Add(_num)

End Sub

Private Sub evenNumbersTimer_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles evenNumbersTimer.Tick

_evenNumber += 2 evenNumbers.Items.Add(_evenNumber)

End Sub

End Class

You’re finished. The first timer simply counts, incrementing a variable by 1 each time the timer fires, before adding the value into the first list box. The second timer, which you’ll remember you set to a different interval, counts by 2, adding the number into the second list box.

The final step is to get that Start button working. Double-click it to drop into the code editor, and key in the following:

Private Sub startButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles startButton.Click

allNumbersTimer.Enabled = Not allNumbersTimer.Enabled evenNumbersTimer.Enabled = Not evenNumbersTimer.Enabled

End Sub

This code simply enables and disables the timers by reversing the value in the timers’ Enabled property.

You’re all set to run it now, so go ahead and run the application. Click the Start button when the form appears to start up the two timers, and then watch what happens (see Figure 18-3).

C H A P T E R 1 8 T H R E A D I N G

485

Figure 18-3. When you run the program and click the Start button, the two lists will update at slightly different intervals, seemingly by themselves.

Also, while the program is still running, try using the user interface; scroll the lists and select items in the lists. What you’ll notice is that even though the program is still running, the timers don’t affect the user interface at all. Even though the user interface is still updating, it still feels snappy, because the main thread is taking a break from time to time to process the code in your timer event handlers.

Timers, though, are usually used when the program needs to do something regularly. Perhaps you are writing an application that needs to check the status of a server from time to time to make sure it’s still working. Or perhaps you need to display a clock in your application that updates in real time.

Tasks such as building a bunch of data behind the scenes, as our “Try It Out” application does, are best placed to use a dedicated thread that runs and runs until it’s complete. That’s where the BackgroundWorker control comes into play.

BackgroundWorker Control

The BackgroundWorker control provides you with an easy route into the full power of writing multithreaded programs. Behind the scenes, the control uses .NET’s built-in support for multithreading—but between you and me, it’s pretty confusing stuff. The BackgroundWorker control is a new addition to the .NET Framework that makes working with threads very simple indeed. For that reason, we won’t even take a look at the behind- the-scenes way of doing things because there really isn’t a need anymore. For the curious, take a look in the online help at the System.Threading namespace.

486 C H A P T E R 1 8 T H R E A D I N G

You’d use a BackgroundWorker control when you have a great deal of processing to do, or an operation that will simply take a long time to run (such as waiting for an email to arrive, for example). You just write your code into a standard method and then call it from the BackgroundWorker control’s event model. This is all easiest to explain with a “Try It Out.”

Try It Out: The BackgroundWorker Control

Let’s write a simple program that calculates factorials. For those of you who, like me, flunked math, a factorial of a number is simply the sum of all numbers between 1 and itself. For example, the factorial of 3 is 6 (1 + 2 + 3). As you can imagine, calculating a factorial for a very high number (say, 10 million) can take a while. Calculating a bunch of factorials without really optimizing the code takes even longer.

Start up a new WinForms project (call it CalcFactorials), and drop some controls onto the default form in the project when it appears, so that it looks like mine in Figure 18-4.

Figure 18-4. Arrange controls on your form so that it looks like this.

You can already see that I’ve set the name on the ListBox control to resultsBox. Do the same in your project, and also set the Name properties for the text box and command button to startingNumber and calcButton, respectively.

C H A P T E R 1 8 T H R E A D I N G

487

Next, double-click the Calculate button to drop into its click event handler. We’ll put our code to calculate factorials in here. Go ahead and type it in:

Public Class Form1

Private Sub calcButton_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles calcButton.Click

resultsBox.Items.Clear()

Dim startingNum As Long = Long.Parse(startingNumber.Text)

For currentNum As

Long = startingNum To startingNum + 999

Dim factorial

As Long = 0

For i As Long

= 1 To currentNum

factorial

+= i

Next resultsBox.Items.Add( _

String.Format("The factorial of {0} is {1}", _ currentNum, factorial))

Next

End Sub

End Class

The code first clears out the list box, so if the user tries to calculate factorial after factorial, the list will clear out each time.

Next, the number entered into the text box is grabbed and used to create a For loop. The For loop contains the code to calculate the factorial for a number and all 999 numbers following it; so, the code calculates 1000 factorials starting at the number the user entered.

Run the program now and enter a starting number of 1000000 (1 million—six zeroes), and click the Calculate button. As soon as you do this, the For loop kicks in, calculating factorials. The problem is that the For loop is doing so much work that the application never gets a chance to redraw its display. The end result is a corrupted user interface, something I’m sure you’ve seen in one or two other Windows applications from time to time.

488

C H A P T E R 1 8 T H R E A D I N G

The BackgroundWorker control solves this little but deeply annoying program by letting us put that factorial code onto another thread.

When the program has finished doing its calculations, stop it.

Back in design mode, double-click the BackgroundWorker control to drop it onto the form. As with the Timer control, the BackgroundWorker is a nonvisual control so it appears underneath the form at design time, as in Figure 18-5.

Figure 18-5. The BackgroundWorker control is a nonvisual control and appears beneath the form at design time.

Leave the control name at its default of backgroundWorker1 and double-click it. You’ll find yourself looking at the control’s DoWork event handler.

When you run your program, it has just one thread, the user interface (UI) thread. When you run a BackgroundWorker control, you get a second thread. Now, a problem with multithreaded applications is that it’s not easy for each thread to talk to another. In our program, for example, we have a need during the calculation process to add the factorials into the list box. The comment here tells us that we aren’t going to be able to do that.

Now, if we were working with the normal behind-the-scenes .NET threading methods, we’d have to write some pretty nasty code to get one thread to update another. The BackgroundWorker control, though, gives us a really handy way to do things. It has a method called ReportProgress(). Calling this method, and passing data to it, causes another event on the control to fire. This event is called ProgressChanged, and I’m

C H A P T E R 1 8 T H R E A D I N G

489

guessing from its name that Microsoft expected this method to be used to update progress bars on forms. What this means to us, though, is that this method does run on the same thread as the UI. So, by calling ReportProgress(), we can get an event on the main thread to fire, and thus update our UI.

Let’s refactor the code we have into the BackgroundWorker events to see how all this works.

First, move all the code, except the first two lines, out of the command button handler and into the BackgroundWorker.DoWork event handler. Your source, with changes highlighted, will look like this:

Public Class Form1

Private startingNum As Long

Private Sub calcButton_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles calcButton.Click

resultsBox.Items.Clear()

startingNum = Long.Parse(startingNumber.Text)

End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _

Handles BackgroundWorker1.DoWork

For currentNum As

Long = startingNum To startingNum + 999

Dim factorial

As Long = 0

For i As Long

= 1 To currentNum

factorial

+= i

Next resultsBox.Items.Add( _

String.Format("The factorial of {0} is {1}", _ currentNum, factorial))

Next

End Sub

End Class

Notice also that I’ve made startingNum a member variable of the form, not a local anymore.