apress pro silverlight 3 in c sharp phần 10 potx

68 390 0
apress pro silverlight 3 in c sharp phần 10 potx

Đ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

CHAPTER 18  ISOLATED STORAGE 656 lblStatus.Text = "Upload started."; } else { lblStatus.Text = "Files must be less than 5 MB."; } } } catch { lblStatus.Text = "Error reading file."; } } } private void client_UploadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) { if (e.Error == null) { lblStatus.Text = "Upload succeeded."; // Refresh the file list. client.GetFileListAsync(); } else { lblStatus.Text = "Upload failed."; } } This completes the example, and gives you a fully functional client that can transfer content to and from the web server. The Last Word In this chapter, you saw how Silverlight allows you to access the local hard drive, but with careful restrictions in place. First, you took a thorough look at isolated storage, the obfuscated, space-limited storage location that you can use to store miscellaneous data, serialized objects, and application settings. Then, you saw how you can use the OpenFileDialog class to retrieve information from a user-selected file anywhere on the hard drive, and how to use SaveFileDialog to perform the reverse feat and write to user-selected file. These features give Silverlight applications an impressive balance of safety and performance, ensuring that malicious applications can’t tamper with local files or read sensitive data but that legitimate software can store details from one user session to the next. 657 CHAPTER 19 ■ ■ ■ Multithreading One of Silverlight’s least expected surprises is its support for multithreading–the fine art of executing more than one piece of code at the same time. It’s a key part of the full .NET Framework and a commonly used feature in rich client applications built with WPF and Windows Forms. However, multithreading hasn’t appeared in the toolkit of most browser- based developers, and it’s notably absent from both JavaScript and Flash. The second surprise is how similar Silverlight’s threading tools are to those in the full .NET Framework. As with ordinary .NET programming, Silverlight developers can create new threads with the Thread class, manage a long-running operation with the BackgroundWorker, and even submit tasks to a pool of worker threads with the ThreadPool. All of these ingredients are closely modeled after their counterparts in the full .NET Framework, so developers who have written multithreaded client applications will quickly find themselves at home with Silverlight. And although there are some clear limitations–for example, you can’t control thread priorities with Silverlight code–these issues don’t stop Silverlight threading from being remarkably powerful. In this chapter, you’ll begin by taking a look at the lower-level Thread class, which gives you the most flexible way to create new threads at will. Along the way, you’ll explore the Silverlight threading model and the rules it imposes. Finally, you’ll examine the higher-level BackgroundWorker class, which gives you a conveniently streamlined, practical way to deal with background tasks. Understanding Multithreading When you program with threads, you write your code as though each thread is running independently. Behind the scenes, the Windows operating system gives each thread a brief unit of time (called a time slice) to perform some work, and then it freezes the thread in a state of suspended animation. A little later (perhaps only a few milliseconds), the operating system unfreezes the thread and allows it to perform a little more work. This model of constant interruption is known as preemptive multitasking. It takes place completely outside the control of your program. Your application acts (for the most part) as though all the threads it has are running simultaneously, and each thread carries on as though it’s an independent program performing some task. CHAPTER 19 ■ MULTITHREADING 658 ■ Note If you have multiple CPUs or a dual-core CPU, it’s possible that two threads will execute at once, but it’s not necessarily likely—after all, the Silverlight plug-in, other applications and services, and the client’s operating system can also compete for the CPU’s attention. Furthermore, the high-level tasks you perform with a programming platform like Silverlight will be translated into many more low-level instructions. In some cases, a dual-core CPU can execute more than one instruction at the same time, meaning a single thread can keep more than one CPU core busy. The Goals of Multithreading Multithreading increases complexity. If you decide to use multithreading, you need to code carefully to avoid minor mistakes that can lead to mysterious errors later. Before you split your application into separate threads, you should carefully consider whether the additional work is warranted. There are essentially three reasons for using multiple threads in a program: • Making the client more responsive. If you run a time-consuming task on a separate thread, the user can still interact with your application’s user interface to perform other tasks. You can even give the user the ability to cancel the background work before it’s complete. By comparison, a single-threaded application locks up the user interface when it performs time-consuming work on the main thread. • Completing several tasks at once. On its own, multithreading doesn’t improve performance for the typical single-CPU computer. (In fact, the additional overhead needed to track the new threads actually decreases performance slightly.) But certain tasks may involve a high degree of latency, like fetching data from an external source (web page, database, or a file on a network) or communicating with a remote component. While these tasks are underway, the CPU is essentially idle. Although you can’t reduce the wait time, you can use the time to perform other work. For example, you can send requests to three web services at the same time to reduce the total time taken, or you can perform CPU-intensive work while waiting for a call to complete. • Making a server application scalable. A server-side application needs to be able to handle an arbitrary number of clients. Depending on the technology you’re using, this may be handled for you (as it is if you’re creating an ASP.NET web application). In other cases, you may need to create this infrastructure on your own–for example, if you’re building a socket-based application with the .NET networking classes, as demonstrated in Chapter 20. This type of design usually applies to .NET-based server applications, not Silverlight applications. In this chapter, you’ll explore an example where multithreading makes good sense: dealing with a time-consuming operation in the background. You’ll see how to keep the application responsive, avoid threading errors, and add support for progress notification and cancellation. CHAPTER 19 ■ MULTITHREADING 659 ■ Tip The CPU is rarely the limiting factor for the performance of a Silverlight application. Network latency, slow web services, and disk access are more common limiting factors. As a result, multithreading rarely improves overall performance, even on a dual-core CPU. However, by improving responsiveness, it can make an application feel much more performant to the user. The DispatcherTimer In some cases, you can avoid threading concerns altogether using the DispatcherTimer class from the System.Windows.Threading namespace. DispatcherTimer was used in Chapter 10 to power the bomb-dropping animations in a simple arcade game. The DispatcherTimer doesn’t offer true multithreaded execution. Instead, it triggers a periodic Tick event on the main application thread. This event interrupts whatever else is taking place in your application, giving you a chance to perform some work. But if you need to frequently perform small amounts of work (for example, starting a new set of bomb-dropping animations every fraction of a second), the DispatcherTimer works as seamlessly as actual multithreading. The advantage of the DispatcherTimer is that the Tick event always executes on the main application thread, thereby sidestepping synchronization problems and the other headaches you’ll consider in this chapter. However, this behavior also introduces a number of limitations. For example, if your timer event-handling code performs a time-consuming task, the user interface locks up until it’s finished. Thus, the timer doesn’t help you make a user interface more responsive, and it doesn’t allow you to collapse the waiting time for high-latency operations. To get this functionality, you need the real multithreading discussed in this chapter. However, clever use of the DispatcherTimer can achieve the effect you need in some situations. For example, it’s a great way to periodically check a web service for new data. As you learned in Chapter 15, all web service calls are asynchronous and are carried out on a background thread. Thus, you can use the DispatcherTimer to create an application that periodically downloads data from a slow web service. For example, it might fire every 5 minutes and then launch the web service call asynchronously, allowing the time-consuming download to take place on a background thread. ■ Note The name of the DispatcherTimer refers to the dispatcher, which controls the main application thread in a Silverlight application. You’ll learn more about the Dispatcher in this chapter. The Thread Class The most straightforward way to create a multithreaded Silverlight application is to use the Thread class from the System.Threading namespace. Each Thread object represents a separate thread of execution. To use the Thread class, you being by creating a new Thread object, at which point you supply a delegate to the method you want to invoke asynchronously. A Thread object can only point to a single method. This signature of this method is limited in several ways. It can’t have a return value, and it must have either no parameters (in which case it matches the ThreadStart CHAPTER 19 ■ MULTITHREADING 660 delegate) or a single object parameter (in which case it matches the ParameterizedThreadStart delegate). For example, if you have a method like this: private void DoSomething() { } you can create a thread that uses it like this: Thread thread = new Thread(DoSomething); After you’ve created the Thread object, you can start it on its way by calling the Thread.Start() method. If your thread accepts an object parameter, you pass it in at this point. thread.Start(); The Start() method returns immediately, and your code begins executing asynchronously on a new thread. When the method ends, the thread is destroyed and can’t be reused. In between, you can use a small set of properties and methods to control the thread’s execution. Table 19-1 lists the most significant. Table 19-1. Members of the Thread Class Property Description IsAlive Returns true unless the thread is stopped, aborted, or not yet started. ManagedThreadId Provides an integer that uniquely identifies this thread. Name Enables you to set a string name that identifies the thread. This is primarily useful during debugging, but it can also be used to distinguish different threads. Once set, the Name property can’t be set again. ThreadState A combination of ThreadState values that indicate whether the thread is started, running, finished, and so on. The ThreadState property should only be used for debugging. If you want to determine whether a thread has completed its work, you need to track that information manually. Start() Starts a thread executing for the first time. You can’t use Start() to restart a thread after it ends. Join() Waits until the thread terminates (or a specified timeout elapses). Sleep() Pauses the current thread for a specified number of milliseconds. This method is static. CHAPTER 19 ■ MULTITHREADING 661 ■ Note Seasoned .NET programmers will notice that the Silverlight version of the Thread class leaves out a few details. In Silverlight, all threads are background threads, you can’t set thread priorities, and you have no ability to temporarily pause and then resume a thread. Similarly, although the Thread class includes an Abort() method that kills a thread with an unhandled exception, this method is marked with the SecurityCritical attribute and so can be called only by the Silverlight plug-in, not by your application code. The challenge of multithreaded programming is communicating between the background thread and the main application thread. It’s easy enough to pass information to the thread when it starts (using parameters). But trying to communicate with the thread while it’s running, or trying to return data when it’s complete, are two more difficult tasks. You may need to use locking to ensure that the same data isn’t accessed on two threads at once (a cardinal sin of multithreaded programming) and marshalling to make sure you don’t access a user interface element from a background thread (an equally bad mistake). Even worse, threading mistakes don’t result in compile-time warnings and don’t necessarily lead to clear, show-stopper bugs. They may cause subtler problems that appear only under occasional, difficult-to-diagnose circumstances. In the following sections, you’ll learn how to use a background thread safely. Marshalling Code to the User Interface Thread Much like .NET client applications (for example, WPF applications and Windows Forms applications), Silverlight supports a single-threaded apartment model. In this model, a single thread runs your entire application and owns all the objects that represent user-interface elements. Furthermore, all these elements have thread affinity. The thread that creates them owns them, and other threads can’t interact with them directly. If you violate this rule–for example, by trying to access a user-interface object from a background thread–you’re certain to cause an immediate exception, a lock-up, or a subtler problem. To keep your application on an even keel, Silverlight uses a dispatcher. The dispatcher owns the main application thread and manages a queue of work items. As your application runs, the dispatcher accepts new work requests and executes one at a time. ■ Note The dispatcher is an instance of the System.Windows.Threading.Dispatcher class, which was introduced with WPF. You can retrieve the dispatcher from any element through the Dispatcher property. The Dispatcher class includes just two members: a CheckAccess() method that allows you to determine if you’re on the correct thread to interact with your application’s user interface, and a BeginInvoke() method that lets you marshal code to the main application thread that the dispatcher controls. CHAPTER 19 ■ MULTITHREADING 662 ■ Tip The Dispatcher.CheckAccess() method is hidden from Visual Studio IntelliSense. You can use it in code; you just won’t see it in the pop-up list of members. For example, the following code responds to a button click by creating a new System.Threading.Thread object. It then uses that thread to launch a small bit of code that changes a text box in the current page: private void cmdBreakRules_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(UpdateTextWrong); thread.Start(); } private void UpdateTextWrong() { // Simulate some work taking place with a five-second delay. Thread.Sleep(TimeSpan.FromSeconds(5)); txt.Text = "Here is some new text."; } This code is destined to fail. The UpdateTextWrong() method will be executed on a new thread, and that thread isn’t allowed to access Silverlight objects. The result is an UnauthorizedAccessException that derails the code. To correct this code, you need to get a reference to the dispatcher that owns the TextBox object (which is the same dispatcher that owns the page and all the other Silverlight objects in the application). When you have access to that dispatcher, you can call Dispatcher.BeginInvoke() to marshal some code to the dispatcher thread. Essentially, BeginInvoke() schedules your code as a task for the dispatcher. The dispatcher then executes that code. Here’s the corrected code: private void cmdFollowRules_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(UpdateTextRight); thread.Start(); } private void UpdateTextRight() { // Simulate some work taking place with a five-second delay. Thread.Sleep(TimeSpan.FromSeconds(5)); // Get the dispatcher from the current page, and use it to invoke // the update code. this.Dispatcher.BeginInvoke((ThreadStart) delegate() { txt.Text = "Here is some new text."; } ); } CHAPTER 19 ■ MULTITHREADING 663 The Dispatcher.BeginInvoke() method takes a single parameter: a delegate that points to the method with the code you want to execute. This can be a method somewhere else in your code, or you can use an anonymous method to define your code inline (as in this example). The inline approach works well for simple operations, like this single-line update. But if you need to use a more complex process to update the user interface, it’s a good idea to factor this code into a separate method, as shown here: private void UpdateTextRight() { // Simulate some work taking place with a five-second delay. Thread.Sleep(TimeSpan.FromSeconds(5)); // Get the dispatcher from the current page, and use it to invoke // the update code. this.Dispatcher.BeginInvoke(SetText); } private void UpdateTextRight() { txt.Text = "Here is some new text."; } ■ Note The BeginInvoke() method also has a return value, which isn’t used in the earlier example. BeginInvoke() returns a DispatcherOperation object, which allows you to follow the status of your marshalling operation and determine when your code has been executed. However, the DispatcherOperation is rarely useful, because the code you pass to BeginInvoke() should take very little time. Remember, if you’re performing a time-consuming background operation, you need to perform this operation on a separate thread and then marshal its result to the dispatcher thread (at which point you’ll update the user interface or change a shared object). It makes no sense to perform your time-consuming code in the method that you pass to BeginInvoke(). For example, this slightly rearranged code still works but is impractical: private void UpdateTextRight() { // Get the dispatcher from the current page. this.Dispatcher.BeginInvoke((ThreadStart) delegate() { // Simulate some work taking place. Thread.Sleep(TimeSpan.FromSeconds(5)); txt.Text = "Here is some new text."; } ); } The problem here is that all the work takes place on the dispatcher thread. That means this code ties up the dispatcher in the same way a non-multithreaded application would. CHAPTER 19 ■ MULTITHREADING 664 Creating a Thread Wrapper The previous example shows how you can update the user interface directly from a background thread. However, this approach isn’t ideal. It creates complex, tightly coupled applications that mingle the code for performing a task with the code for displaying data. The result is an application that’s more complex, less flexible, and difficult to change. For example, if you change the name of the text box in the previous example, or replace it with a different control, you’ll also need to revise your threading code. A better approach is to create a thread that passes information back to the main application and lets the application take care of the display details. To make it easier to use this approach, it’s common to wrap the threading code and the data into a separate class. You can then add properties to that class for the input and output information. This custom class is often called a thread wrapper. Before you create your thread wrapper, it makes sense to factor out all the threading essentials into a base class. That way, you can use the same pattern to create multiple background tasks without repeating the same code each time. You’ll examine the ThreadWrapperBase class piece by piece. First, you declare the ThreadWrapperBase with the abstract keyword so it can’t be instantiated on its own. Instead, you need to create a derived class. public abstract class ThreadWrapperBase { } The ThreadWrapperBase defines one public property, named Status, which returns one of three values from an enumeration (Unstarted, InProgress, or Completed): // Track the status of the task. private StatusState status = StatusState.Unstarted; public StatusState Status { get { return status; } } The ThreadWrapperBase wraps a Thread object. It exposes a public Start() method which, when called, creates the thread and starts it: // This is the thread where the task is carried out. private Thread thread; // Start the new operation. public void Start() { if (status == StatusState.InProgress) { throw new InvalidOperationException("Already in progress."); } else { // Initialize the new task. status = StatusState.InProgress; // Create the thread. thread = new Thread(StartTaskAsync); CHAPTER 19 ■ MULTITHREADING 665 // Start the thread. thread.Start(); } } The thread executes a private method named StartTaskAsync(). This method farms out the work to two other methods: DoTask() and OnCompleted(). DoTask() performs the actual work (calculating prime numbers). OnCompleted() fires a completion event or triggers a callback to notify the client. Both of these details are specific to the particular task at hand, so they’re implemented as abstract methods that the derived class will override: private void StartTaskAsync() { DoTask(); status = StatusState.Completed; OnCompleted(); } // Override this class to supply the task logic. protected abstract void DoTask(); // Override this class to supply the callback logic. protected abstract void OnCompleted(); This completes the ThreadWrapperBase class. Now, you need to create a derived class that uses it. The following section presents a practical example with an algorithm for finding prime numbers. Creating the Worker Class The basic ingredient for any test of multithreading is a time-consuming process. The following example uses a common algorithm called the sieve of Eratosthenes for finding prime numbers in a given range, which was invented by Eratosthenes in about 240 BC. With this algorithm, you begin by making a list of all the integers in a range of numbers. You then strike out the multiples of all primes less than or equal to the square root of the maximum number. The numbers that are left are the primes. In this example, you won’t consider the theory that proves the sieve of Eratosthenes works or the fairly trivial code that performs it. (Similarly, don’t worry about optimizing it or comparing it against other techniques.) However, you will see how to perform the sieve of Eratosthenes algorithm on a background thread. The full code for the FindPrimesThreadWrapper class is available with the online examples for this chapter. Like any class that derives from ThreadWrapperBase, it needs to supply four things: • Fields or properties that store the initial data. In this example, those are the from and to numbers that delineate the search range. • Fields or properties that store the final data. In this example, that’s the final prime list, which is stored in an array. [...]... when the user clicks the Cancel button: private void cmdCancel_Click(object sender, RoutedEventArgs e) { backgroundWorker.CancelAsync(); } Nothing happens automatically when you call CancelAsync() Instead, the code that’s performing the task needs to explicitly check for the cancel request, perform any required cleanup, and return Here’s the code in the FindPrimes() method that checks for cancellation... that indicates a stop is requested private bool cancelRequested = false; protected bool CancelRequested { get { return cancelRequested; } } // Call this to request a cancel public void RequestCancel() { cancelRequested = true; } // When cancelling, the worker should call the OnCancelled() method // to raise the Cancelled event public event EventHandler Cancelled; protected void OnCancelled() { if (Cancelled... domains, which means your Silverlight code can’t retrieve any of its content: If you need to access web content from a website that doesn’t allow cross-domain... that can be accessed by Flash applications can also be accessed by Silverlight applications The clientaccesspolicy.xml or crossdomain.xml file must be stored in the web root So, if you attempt to access web content with the URL www.somesite.com/~luther/services/CalendarService.ashx, Silverlight checks for www.somesite.com/clientaccesspolicy.xml and then (if the former isn’t found) www.somesite.com/crossdomain.xml... callback runs on a background thread (not the main application thread), it 688 CHAPTER 20 NETWORKING can’t directly access the elements in the page As you saw in Chapter 19, you can work around this problem using Dispatcher.BeginInvoke() However, copying the search year sidesteps the problem Typically, Silverlight calls your CreateRequest() method a fraction of a second after you call BeginGetRequestStream()... application Of course, just because you can write a multithreaded Silverlight application doesn’t mean you should Before you delve too deeply into the intricacies of multithreaded programming, it’s worth considering the advice of Microsoft architects Because of the inherent complexity of deeply multithreaded code, especially when combined with dramatically different operating systems and hardware, Microsoft’s... true; } } The ProcessRequest() method does the actual work It receives an HttpContext object through which it can access the current request details and write the response In this example, ProcessRequest() checks for a posted value named year It then checks if the year string includes the letters, and gets the corresponding population statistic using a custom method called GetPopulation (which isn’t shown)... http://www.flickr.com, but it will let you access http://api.flickr.com ■ Tip Before you attempt to use the examples in this chapter with different websites, you should verify that they support cross-domain access To do so, try requesting the clientaccesspolicy.xml and crossdomain.xml files in the root website 680 CHAPTER 20 NETWORKING In Chapter 15, you learned what the clientaccesspolicy.xml file looks... quick and easy local connection feature that allows the Silverlight applications that are running on the same computer to communicate You’ll learn about this feature in the “Local Connections” section at the end of this chapter Interacting with the Web In Chapter 6, you saw how you can use the WebClient class to download a file from the Web This technique allows you to grab a resource or even a Silverlight. .. MULTITHREADING Figure 19-2 Tracking progress for an asynchronous task Supporting Cancellation It’s just as easy to add support for canceling a long-running task with the BackgroundWorker The first step is to set the BackgroundWorker.WorkerSupportsCancellation property to true To request a cancellation, your code needs to call the BackgroundWorker.CancelAsync() method In this example, the cancellation is requested . Dispatcher class includes just two members: a CheckAccess() method that allows you to determine if you’re on the correct thread to interact with your application’s user interface, and a BeginInvoke(). // When cancelling, the worker should call the OnCancelled() method // to raise the Cancelled event. public event EventHandler Cancelled; protected void OnCancelled() { if (Cancelled !=. private bool cancelRequested = false; protected bool CancelRequested { get { return cancelRequested; } } // Call this to request a cancel. public void RequestCancel() { cancelRequested

Ngày đăng: 06/08/2014, 10:20

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan