1. Trang chủ
  2. » Công Nghệ Thông Tin

Visual Basic 2005 Design and Development - Chapter 21 pptx

24 143 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 24
Dung lượng 560 KB

Nội dung

Threading Most computers today have only a single central processing unit (CPU) that executes instructions. That means they can only do one thing at a time. Modern CPUs are pretty fast and can execute many millions or even billions of instructions per second. Most applications don’t require that much speed, so the computer splits the CPU’s time to let several different applications take turns running. The CPU is so fast that the switching takes place quickly enough to give the illusion that the computer is running more than one program at the same time. While the CPU can really only execute one instruction at a time, it appears as if the Windows operating system is running a whole bunch of applications simultaneously. Each of the programs running on the computer is a separate process. Within a process, you can cre- ate multiple threads of execution that can also run simultaneously. Every program has at least one process and thread, but some create more. These threads are more closely related than the pro- cesses representing different applications, but the idea is similar. Like processes, the threads take turns running on the CPU so they give the illusion of running at the same time. Typically, threads must communicate and work together to provide whatever application it is that they build as a group. To avoid confusion, the operating system imposes strict rules on how threads can communicate safely. It’s easier for different threads to communicate than it is for dif- ferent processes because they are more closely related, but there are still rules to follow. For example, you could build a single application that monitors a stock quote Web site to track your favorite stocks. Separate threads could monitor each of several stocks and display their cur- rent prices on separate graphs within the same application. Each thread works separately, but they all interact with the main program’s form to display their data. This chapter describes some useful threading scenarios. It explains how to build applications that use threading to make the user interface more responsive, to perform tasks in the background while the user does other things, and to divide complex tasks into pieces. 28_053416 ch21.qxd 1/2/07 6:36 PM Page 535 Threading Pros and Cons The technology used to build computer chips is reaching its physical limits, so the steady increases in CPU speed that developers have come to expect over the last few decades is starting to slow down. Because they are already using the fastest chips available, computer manufacturers cannot rely on even faster chips to improve performance. Instead, many computers now use multiple CPUs to improve per- formance. It is quite common these days to find computers using two CPUs, and it seems likely that the trend toward multiple CPUs will continue. Systems with up to eight processors will probably be avail- able by the time you read this book. The exact definition of these systems is sometimes a little fuzzy. Some “multi-core” systems have sepa- rate CPU chips, while others have multiple processors on a single chip. Some architectures have a cen- tral processor that controls several secondary processors. For the purposes of this chapter, all that matters is that the computer has more than one processor of some kind that can execute threads. If you want to take full advantage of multi-CPU systems, you should know a bit about multi-threaded applications. The following sections describe some of the advantages and disadvantages of multi- threaded programming. Advantages Often, a multiple CPU system can execute different threads on different processors. In that case, if your code produces multiple threads, the program will run faster because each thread can run on a separate processor. The program probably won’t run twice as fast, because there is some overhead in coordinat- ing the threads, but it will probably run significantly faster than it would on a single CPU system. Some multi-processor systems can automatically parallelize instructions and run pieces of the applica- tion in separate threads. For example, suppose a program must perform some calculations on some old values, read some new values from a file, and then combine the old and new values. The computer could start a thread running on one CPU to read values from the file, while another thread on a separate CPU performs the initial calculations. Then, the threads would need to synchronize so that they can combine the new and old values. Though the entire sequence cannot be split across two processors, some of the steps can be. Even if the computer has a single processor, writing multiple threads can often make the application appear faster and more responsive to the user. For example, suppose a program must perform a very long series of calculations. In a single-threaded application, the program begins its calculation and can- not respond until it finishes. A well-written application provides feedback so that the user knows the program is still working. A poorly written application may appear stuck and might not even refresh its user interface. If the user covers and exposes the program, its form may not even redraw. To keep the application responsive, you can start the long calculation on a separate thread. The system automatically switches control between this calculation thread and the application’s user interface thread (UI thread) so that the user interface can update itself. 536 Part IV: Specific Techniques 28_053416 ch21.qxd 1/2/07 6:36 PM Page 536 The user interface can also respond to user actions such as button clicks. In particular, the user may be able to click a button to cancel the long calculation. A program can also control a thread’s priority. The system gives precedence to higher-priority threads. You can make high-priority threads run important calculations, while lower-priority threads perform less-important tasks when the high-priority threads are idle. For example, you can perform long calcula- tions or downloads on a lower-priority thread, so the user interface takes precedence. Because the user interface spends most of its time waiting for user input, that gives the lower-priority thread plenty of time to execute, but the user interface can still respond when the user does something to it. Disadvantages Despite the advantage of increased responsiveness (and possibly performance), threading has some seri- ous drawbacks. The biggest disadvantage to multi-threading is that it is confusing. Multi-threading code is more compli- cated and non-intuitive than code written for a single thread. It requires extra coordination among the threads and safe communications (particularly between other threads and the thread controlling the user interface). It requires additional work to ensure that the threads get access to the resources they need without wasting time competing for them. These issues can lead to some very confusing bugs. Often, these bugs depend on the exact timing of a series of calculations. Execution timing is handled by the operating system and is out of your control so it can be very hard to reliably reproduce these bugs. The two most problematic types of bugs caused by multi-threading issues are race conditions and deadlocks. Race Conditions For example, suppose a program uses two threads to calculate new values for a graph. The shared variables old_x and old_y record the coordinates of the last value plotted. When a thread receives a new Y value, it increments X, draws a line from the old point to the new one, and updates the old_x and old_y variables to hold the new point’s coordinates. The following pseudocode shows how the threads work: 1: Get new_y 2: new_x = old_x + 1 3: Line (old_x, old_y)-(new_x, new_y) 4: old_x = new_x 5: old_y = new_y Suppose old_x = 10 and old_y = 20. Suppose also that Thread A calculates a new Y value of 25 and Thread B calculates a new Y value of 30. The following timeline shows a normal expected execution for the threads where Thread A executes all of its statements before Thread B executes. The result is a line from (10, 20) to (11, 25) and another line from (11, 25) to (12, 30). 537 Chapter 21: Threading 28_053416 ch21.qxd 1/2/07 6:36 PM Page 537 Thread A Thread B 1A: new_y = 25 2A: new_x = 11 3A: (10, 20)-(11, 25) 4A: old_x = 11 5A: old_y = 25 1B: new_y = 30 2B: new_x = 12 3B: (11, 25)-(12, 30) 4B: old_x = 12 5B: old_y = 30 Now, consider the following timeline: Thread A Thread B 1A: new_y = 25 2A: new_x = 11 3A: (10, 20)-(11, 25) 1B: new_y = 30 2B: new_x = 11 3B: (10, 20)-(11, 30) 4B: old_x = 11 5B: old_y = 30 4A: old_x = 11 5A: old_y = 25 In this execution sequence, Thread A starts running its code, but the system interrupts it before it fin- ishes. Thread A calculates its new_y and new_x values and draws a line from the old point (10, 20) to its new point (11, 25). Now Thread B takes over. Because Thread A has not yet updated old_x and old_y, Thread B uses the original values 10 and 20, so it draws a line from (10, 20) to its new point (11, 30). Thread B updates old_x and old_y to the new coordinates (11, 30). Thread A resumes and updates old_x and old_y again, setting them to the coordinates (11, 25). Future lines start at this point, not from the point (11, 30) saved by Thread B. The result is an extra line sticking up from the graph between points (10, 20) and (11, 30). 538 Part IV: Specific Techniques 28_053416 ch21.qxd 1/2/07 6:36 PM Page 538 This type of bug is a type of race condition. A race condition occurs when two threads race toward a com- mon piece of data, and the outcome depends on which thread gets there first. In this example, Thread B updates the old_x and old_y variables first, and then Thread A overwrites those values. This problem would be confusing enough if the steps executed in the same order shown in the timeline every time, but, in general, you don’t know when the operating system will decide that it’s time to switch between Thread A and Thread B. If you look carefully at the timeline, you can see that the problem occurs when Thread B executes between Thread A’s steps 3A and 4A. Here, Thread A has drawn its line, but has not yet updated old_x and old_y. If Thread A contains about 100 lines of code, and given no other information, the odds that Thread B will start running between those steps is about 1 in 100. After testing the code 100 times, you would only have about a 63 percent chance of seeing the bug. You would need to test the code around 300 times to have a 95 percent chance of discovering the bug. To calculate the probability of finding the bug in N trials, calculate 1 minus the probability of not find- ing the bug raised to the power N. In this case, 1 – 0.99 ^ 300 is approximately 0.95. At that point, you would know that a bug exists, but it would be very hard to reproduce. You cannot simply step through the code and see what it is doing the way you can with single-threaded code. You not only need to consider what the two threads are doing, but also the exact timing of their execution. One way to avoid this kind of race condition is to make each thread “lock” a variable before using it. If the variable is locked by another thread, the code blocks until the other thread releases its lock before continuing. The following table shows the previous timeline modified by making the threads lock the variables new_x, new_y, old_x, and old_y. Thread A Thread B 0A: Lock new_x, new_y old_x, old_y 1A: new_y = 25 2A: new_x = 11 3A: (10, 20)-(11, 25) 0B: Lock new_x, new_y old_x, old_y 4A: old_x = 11 <blocked> 5A: old_y = 25 <blocked> 6A: Release locks <blocked> 1B: new_y = 30 2B: new_x = 12 3B: (11, 25)-(12, 30) 4B: old_x = 12 5B: old_y = 30 6B: Release locks 539 Chapter 21: Threading 28_053416 ch21.qxd 1/2/07 6:36 PM Page 539 In this version, when Thread B starts, it tries to lock the variables and blocks until Thread A releases its locks. That gives Thread A time to finish before Thread B resumes. When Thread B resumes, old_x and old_y have been updated, so the result is the same as if Thread A had run completely before Thread B started. Deadlocks Although locks can prevent race conditions, they lead to a new type of multi-threading bug called a deadlock. A deadlock occurs when one thread is blocked, waiting for another thread to release a lock while the other thread is waiting for the first thread to release a different lock. For example, consider the following timeline. Here, Thread A tries to lock variable X, and then variable Y, while Thread B tries to lock Y and then X. Thread A Thread B 1A: Lock X 1B: Lock Y 2B: Lock X 2A: Lock Y <blocked> <blocked> <blocked> In this example, Thread A locks X and Thread B locks Y. Then Thread B tries to lock X and blocks because Thread A already has that variable locked. Next, Thread A tries to lock Y and blocks, because Thread B already has it locked. Both threads are blocked waiting for each other, so execution stops. You can fix this simple example by making the two threads try to lock X and Y in the same order, but in more complicated examples a solution isn’t always obvious. For example, suppose Threads A, B, and C need to lock resources X, Y, and Z. Thread A starts and locks resource X before the CPU switches to run Thread B. Thread B locks resource Y and then is interrupted while Thread C takes over. Thread C locks resource Z and then tries to lock resources X and Y. Because all three resources are locked, all three of the threads are blocked. Requiring the threads to lock the resources in a specific order works for the three-thread scenarios, as well. If all three threads try to lock the resources in the order X, Y, Z, then whichever thread locks resource X first gets access to the others. However, resources don’t always have nicely ordered names, so it’s not always clear what order would be best. Suppose Thread A needs to perform a long task with resource X and short tasks with resources Y and Z. In that case, it makes sense for Thread A to use resource X first so that it keeps the other resources locked for the shortest time possible. Similarly, suppose Thread B must do a lot of work with resource Y, and Thread C must spend a lot of time working with resource Z. In this case, there are good reasons for the threads to grab the resources in different orders. If different developers are working independently on the code for the different threads, they need to coordinate carefully to avoid deadlocks. To make matters worse, suppose the program has half a dozen threads accessing a dozen resources. Suppose some of the threads call library routines that might access the resources. You didn’t write the 540 Part IV: Specific Techniques 28_053416 ch21.qxd 1/2/07 6:36 PM Page 540 library routines, so you can’t dictate the locking order they use. To further confuse the situation, suppose the computer is running other applications that also need to use those resources. It would be very diffi- cult to figure out how to coordinate all of these threads, library calls, and processes, some of which are out of your control, so they cannot deadlock. A different approach to preventing deadlocks is to make lock attempts time out after awhile. In the pre- vious example, if Thread B cannot lock variable Y within 1 second, it gives up, releases its lock on X, and tries again later. That lets Thread A get its lock on Y and continue. Later, Thread B tries again and, assuming Thread A has finished and released its locks, it can continue. You can make time-outs a little more effective if they use random durations. In this example, it is possi- ble that Thread A would also time out waiting for resource Y, just as Thread B times out waiting for resource X. Thread A then locks resource X again and is interrupted, while Thread B locks resource Y again. In practice, the operating system will be inconsistent enough to add a little randomness to the process, so this pattern is unlikely to repeat, but it is possible. You can reduce the chances slightly by introducing a bit of randomness to the time-out lengths. For example, Thread A might time out after 1.2 seconds, while Thread B times out after 0.9 seconds. As is the case with race conditions, deadlocks depend on the exact execution timing. This example only causes problems if Thread B runs between steps 1A and 2A. Like race conditions, deadlocks are hard to reproduce, but they are a little easier to analyze when they occur because the threads stop running. A race condition leaves behind corrupt data and keeps running, but in a deadlock, you can at least tell which threads are stuck. Performance Issues Multi-threading can improve performance on multi-CPU systems and can improve responsiveness even on single-CPU systems, but it isn’t free. The operating system needs to use extra resources to keep track of multiple threads. It also takes some time to switch from one thread to another. This extra overhead actually slows down total execution, at least on single-CPU systems. If you run enough threads, the additional overhead may even slow down multi-CPU systems. If you have a lot of threads, the system may spend more time switching back and forth than running code. Applications that typically benefit from multi-threading include those that combine relatively slow and relatively fast operations. For example, many applications spend most of their time waiting for the user to do something. The program sits idle until the user clicks a button, selects a menu item, or enters text. While the program is sitting idle, it could be running threads in the background to perform calculations. Similarly, if the program spends a lot of time waiting for network downloads, disk access, Web Service calls, and other slow operations, other threads could do something more active at the same time. One technique for dynamically deciding whether it’s worth starting another thread is to check the sys- tem’s CPU load. If the CPU is 50 percent used or less, another thread will probably provide a net gain in performance. If CPU usage is higher, such as 75 or 80 percent, adding another thread might degrade per- formance overall. Use the PerformanceCounter class to check the CPU load. You might want to look at a time average over several seconds because the CPU load can fluctuate quickly. 541 Chapter 21: Threading 28_053416 ch21.qxd 1/2/07 6:36 PM Page 541 Using Background Workers To take advantage of multi-threading without stumbling into its pitfalls, it helps to keep things as simple as possible. Although Visual Basic provides tools for building extremely powerful multi-threaded appli- cations, if you aren’t careful, you can quickly build a program that is so complicated and confusing that you can’t get it to work. If you use multi-threading in the simplest way that can get the job done, you can minimize your risks. Visual Basic provides a BackgroundWorker component that makes threading easier and safer than it is if you use the underlying threading tools directly. BackgroundWorker provides events to perform work, provide the main program with progress reports, and notify the program when it has finished. Because they are safe and easy, most of the examples presented in this chapter use BackgroundWorkers to man- age threading. UI Threading One of the most common reasons to use multiple threads is to allow the user interface (UI) to respond quickly while some longer task executes in the background. While a long download or calculation works in the background, the UI can still process button clicks and other user actions. In particular, it can han- dle the user’s command to stop the background process. I’ve run into two main problems with this kind of threading. First, the program may have trouble if the user interface can interact directly with the background process. For example, suppose clicking a Download button makes the program start a long calculation on a background thread. Now, suppose the user accidentally double-clicks this button. The program starts a new thread to perform the calculation and then starts another thread to perform the same calculation. At best, the two threads will split the CPU’s cycles and cut performance in half. At worst, the two threads may interfere with each other, caus- ing race conditions or a deadlock. During beta testing on one project, we discovered exactly this problem. A user could launch a process by clicking a button. One tester found that if he quickly clicked the button again, he could start a second process, so he wrote it up as a bug report. He then found that if he clicked really quickly three times, he could make three processes run simultaneously, so he wrote that up separately. He went on to spend the afternoon reporting the same bug in about a dozen different ways. It probably wasn’t the best use of his testing time, but at least we got to clear a lot of bug reports with a single fix. The easiest way to handle this sort of problem is to disable the user interface elements that launch the background thread while the thread is running. In this example, the program should disable the Download button before starting the thread. When the thread finishes, the program should re-enable the button. The second major problem I’ve seen with UI threading is caused by the fact that a thread cannot directly interact with controls that it did not create. It would be perfectly natural for a thread that performs some calculation to display periodic progress messages and its final result on the main program’s controls. Unfortunately, that isn’t allowed. Instead, the thread must use the Invoke method to call a routine defined on some control. Invoke passes the call to the thread that created the control so that it can execute the routine. 542 Part IV: Specific Techniques 28_053416 ch21.qxd 1/2/07 6:36 PM Page 542 Usually a developer adds an update routine to the form and then uses Invoke to call that routine. The update routine can then access any control on the form. Example program BackgroundGraphs (shown in Figure 21-1 and available for download at www.vb- helper.com/one_on_one.htm ) uses four BackgroundWorker controls to display four graphs. Each BackgroundWorker uses Invoke to make the main UI thread update the corresponding graph. Figure 21-1: Program BackgroundGraphs displays four graphs with different shapes running at different speeds. The following code shows how program BackgroundGraphs loads and starts its BackgroundWorker controls. (You can download this example at www.vb-helper.com/one_on_one.htm.) To make updat- ing the graphs easier, the form’s Load event handler saves references to their PictureBoxes in the m_Graphs array. It also makes Bitmaps to hold each graph’s image. The code then sets initial data val- ues for the four graphs and calls the BackgroundWorkers’ RunWorkerAsync methods to make them start running asynchronously. ‘ The graphing PictureBoxes. Private m_Graphs() As PictureBox Private m_Wid As Integer Private m_Hgt As Integer ‘ The graphs’ images. Private m_Bitmaps() As Bitmap Private m_LastY() As Integer Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ‘ Save the PictureBoxes in an array. m_Graphs = New PictureBox() {picGraph0, picGraph1, picGraph2, picGraph3} ‘ Get the PictureBoxes’ dimensions. 543 Chapter 21: Threading 28_053416 ch21.qxd 1/2/07 6:36 PM Page 543 m_Wid = picGraph0.ClientSize.Width m_Hgt = picGraph0.ClientSize.Height ‘ Make the bitmaps. Dim num_graphs As Integer = m_Graphs.Length ReDim m_Bitmaps(0 To num_graphs - 1) ReDim m_LastY(0 To num_graphs - 1) For i As Integer = 0 To 3 m_Bitmaps(i) = New Bitmap(m_Wid, m_Hgt) Using gr As Graphics = Graphics.FromImage(m_Bitmaps(i)) gr.Clear(Color.White) End Using m_Graphs(i).Image = m_Bitmaps(i) Next i ‘ Start the background workers. m_LastY(0) = Y0(0) bgwGraph0.RunWorkerAsync() m_LastY(1) = m_Hgt \ 2 bgwGraph1.RunWorkerAsync() m_LastY(2) = m_Hgt \ 2 bgwGraph2.RunWorkerAsync() m_LastY(3) = Y3(0) bgwGraph3.RunWorkerAsync() End Sub When the program calls a BackgroundWorker’s RunWorkerAsync method, the worker’s DoWork event fires. The event handler should perform whatever task the worker is supposed to handle. The following code shows how the first worker generates its graph. The other graphs work similarly, so their code isn’t shown here. ‘ Draw graph 0. Private Sub bgwGraph0_DoWork(ByVal sender As System.Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwGraph0.DoWork ‘ Work at reduced priority. Thread.CurrentThread.Priority = ThreadPriority.BelowNormal Static X As Integer = 0 Do ‘ Calculate the next point. X += 1 ‘ Report the point to the UI thread. Me.Invoke(Me.m_PlotPointDelegate, 0, Y0(X)) ‘ Pause a while. Thread.Sleep(1) ‘ See if we should cancel. If bgwGraph0.CancellationPending Then Exit Do 544 Part IV: Specific Techniques 28_053416 ch21.qxd 1/2/07 6:36 PM Page 544 [...]... same time Example program SyncLockTest (shown in Figure 2 1-7 and available for download at www.vb-helper com/one_on_one.htm) lets you experiment with the SyncLock statement This program is more contrived than program MandelbrotWorkers, but it is much easier to understand 554 28_053416 ch21.qxd 1/2/07 6:36 PM Page 555 Chapter 21: Threading Figure 2 1-7 : Program SyncLockTest lets you experiment with the... program stops work and displays the parts of the image that it has built so far Figure 2 1-3 shows the program after it has generated just over one-half of an image Figure 2 1-3 : Program MandelbrotBackground lets you interrupt Mandelbrot Set calculations 547 28_053416 ch21.qxd 1/2/07 6:36 PM Page 548 Part IV: Specific Techniques This example is actually fairly useful for navigating the Mandelbrot Set when... MandelbrotBackground program (available for download at www.vb-helper.com/one_on_ one.htm) draws the Mandelbrot Set If you click the Full Scale button, or click and drag to select an area, the program zooms in on that part of the set While it is building the new image, the program displays the percentage of the image that it has built in its title bar, as shown in Figure 2 1-2 Figure 2 1-2 : Program MandelbrotBackground... that more than one thread will try to modify an image at the same time Figure 2 1-5 shows program MandelbrotWorkers drawing an image In this image, the program was using two BackgroundWorker components, and each has had some time to update the display Figure 2 1-5 : Program MandelbrotWorkers uses multiple BackgroundWorkers to draw Mandelbrot Sets If two threads try to set the colors of their bitmaps’ pixels... the rest If i = NUM_WORKERS - 1 Then xmax = wid - 1 ‘ Prepare the worker’s assignment Dim worker_info As New WorkerInfo( _ Me, xmin, xmax, 0, hgt - 1, dReaC, dImaC) ‘ Start the worker m_Workers(i).RunWorkerAsync(worker_info) xmin += dxmin - 1 Next i 552 28_053416 ch21.qxd 1/2/07 6:36 PM Page 553 Chapter 21: Threading This version of the DoWork event handler is similar to the previous versions with... Integer = 0 To wid - 1 For j As Integer = 0 To hgt – 1 ‘ Calculate and set the color for pixel (i, j) ‘ Next j If i Mod 10 = 0 Then ‘ Report our progress Dim progress As Integer = CInt(100 * i / wid) bgwMandelbrot.ReportProgress(progress) ‘ See if we should cancel ‘Thread.Sleep(1) ‘ Not needed if priority is lowered 548 28_053416 ch21.qxd 1/2/07 6:36 PM Page 549 Chapter 21: Threading If bgwMandelbrot.CancellationPending... The top-level error discovered by the application is “Exception has been thrown by the target of an invocation.” If you look at the inner exception, you’ll find that the original error is “Object is currently in use elsewhere.” Figure 2 1-6 shows the detail for the error with the InnerException property expanded 553 28_053416 ch21.qxd 1/2/07 6:36 PM Page 554 Part IV: Specific Techniques Figure 2 1-6 : If... Mandelbrot Set as it is being constructed, as well as its percentage complete Figure 2 1-4 shows the program when it has built 73 percent of the new image Figure 2 1-4 : Program MandelbrotShowProgress displays partial images as it builds them This example works much as program MandelbrotBackground does When its DoWork event handler calls the worker’s ReportProgress method, however, it also passes in the partially... program’s BackgroundWorker finishes building the new Mandelbrot image, its DoWork event handler exits and it raises its RunWorkerCompleted event The main program uses the following code to catch this event and display the image built by the worker and contained in the e.Result parameter: ‘ The worker is done Display the new image Private Sub bgwMandelbrot_RunWorkerCompleted(ByVal sender As Object, _... Using gr As Graphics = Graphics.FromImage(bm) gr.DrawImage(m_Bitmaps(graph_num), -1 , 0) ‘ Clear the right edge gr.DrawLine(Pens.White, m_Wid - 1, 0, m_Wid - 1, m_Hgt) ‘ Draw the new point 545 28_053416 ch21.qxd 1/2/07 6:36 PM Page 546 Part IV: Specific Techniques gr.DrawLine(Pens.Blue, m_Wid - 2, m_LastY(graph_num), m_Wid - 1, Y) End Using ‘ Save the new Y value m_LastY(graph_num) = Y ‘ Refresh the graph . stops work and dis- plays the parts of the image that it has built so far. Figure 2 1-3 shows the program after it has generated just over one-half of an image. Figure 2 1-3 : Program MandelbrotBackground. elsewhere.” Figure 2 1-6 shows the detail for the error with the InnerException property expanded. 553 Chapter 21: Threading 28_053416 ch21.qxd 1/2/07 6:36 PM Page 553 Figure 2 1-6 : If multiple threads. responsiveness (and possibly performance), threading has some seri- ous drawbacks. The biggest disadvantage to multi-threading is that it is confusing. Multi-threading code is more compli- cated and non-intuitive

Ngày đăng: 14/08/2014, 11:20