WPF thread, schedulingProgramBuild apps that respond faster
By: Shawn wildermuth
From: http://msdn.microsoft.com/msdnmag/issues/07/10/WPFThreading/default.aspx? Loc = ZH #
If it takes several months to create an intuitive, natural, or even exquisite interface, but the result is that the user has to tap his finger on their combination desk to wait for the program to respond, this will make people feel humiliated. The screen of the application is stuck because of a long running process. It is a pain to see this situation. However, creating a responsive application requires careful planning, which usually requires that long-running processes work in other threads to release the UI thread, keep up with the user's progress at any time.
For the first time, the real response speed can be traced back to Visual C ++.With MFC and the first grid I have written. At that time, I was helping develop a pharmaceutical application that had to be able to display each type of drug in complex prescriptions. The problem is that there are 30,000 kinds of drugs, so we decided to first fill the first full screen drug in the UI thread (about 50 milliseconds), giving a Rapid Response impression, then the background thread is used to complete the filling of invisible drugs (about 10 seconds ). The project runs well and I have learned a very valuable experience, that is, user perception can be more important than reality.
WindowsPresentation Foundation (WPF) is an outstanding technology, but it does not mean that you do not need to consider the responsiveness of applications. Regardless of the type of the related long-running process (whether it is to obtain a large number of results from the database, make asynchronous web service calls, or any number of other potentially intensive operations), the simple fact is that, a faster response application is a long-term guarantee to make users more satisfied. However, it is important to understand the WPF thread model before using the asynchronous programming model in a WPF application. In this article, I will not only introduce you to this thread model, but also show you how the scheduling program-based objects work, explains how to use backgroundworker to create an attractive and Responsive user interface.
Thread Model
When all WPF applications are started, two important threads are loaded: one for displaying the user interface and the other for managing the user interface. The rendering thread is a hidden thread running in the background. Therefore, the only thread you normally face is the UI thread. WPF requires that most of its objects be associated with the UI thread. This is called thread Association, which means that a WPF object can only be used on the thread where it is created. Using it on other threads causes a running exception. Note that the WPF thread model can beAPI for smooth interaction. This means that WPF can host or host any hwnd-based API (Windows Forms, Visual Basic, MFC, or even Win32 ).
Thread associations are processed by the dispatcher class, which is a message loop that is used for WPF applications and sorted by priority. Generally, a WPF project has a single dispatcher object (so there is a single UI thread), and all user interface work uses it as a channel.
Unlike a typical message loop, each work item sent to WPF is sent by dispatcher at a specific priority. This allows you to sort projects by priority and delay some type of work until the system has time to process them. (For example, some work items can be delayed until the system or application is idle .) Projects are prioritized so that WPF can grant more permissions to some type of work, so it has more time on the thread than other jobs.
Later in this article, I will explain that the presentation engine has a higher priority than the input system in terms of updating the user interface. This means that no matter whether the user is using a mouse, keyboard or ink printing system, the animation will continue to update the user interface. This makes the user interface seem to respond faster. For example, let's assume that you are writing a music play application (similar to Windows MediaPlayer ). Whether or not you are using the interface, you are most likely to display information about music playback (including progress bars and other information ). For users, this allows the interface to respond more quickly to the things they are most interested in (in this example, listening to music.
In addition to using the dispatcher message loop to direct the work project to the user interface thread, each WPF object can also perceive the dispatcher (and the UI thread it depends on) that is responsible for it ). This means that any attempt to update the WPF object from the second thread will fail. This is the responsibility of the dispatcherobject class.
Dispatcherobject
In the class hierarchy of WPF, most of them are derived from the dispatcherobject class (through other classes ). For exampleFigure 1As shown in, you can see that the dispatcherobject virtual class is located exactly between the lower side of the object and the hierarchy of most WPF classes.
Figure 1 Dispatcherobject Derivation
The dispatcherobject class has two main responsibilities: to provide access permissions to the current dispatcher associated with the object, as well as to provide methods to check (checkaccess) and verify (verifyaccess) whether a thread has the right to access the object (derived from dispatcherobject ). The difference between checkaccess and verifyaccess is that checkaccess returns a Boolean value indicating whether the current thread can use objects. verifyaccess causes an exception when the thread has no permission to access objects. By providing these basic functions, all WPF objects support determining whether they can be used in specific threads (especially UI threads. If you are writing your own WPF objects (such as controls), you should call verifyaccess before executing any work. This ensures that your object is only used on the UI thread, as shown in figure 2.
Therefore, when calling any dispatcherobject derived objects such as control, window, and panel, you should note that they are on the UI thread. If you call dispatcherobject from a non-UI thread, an exception is thrown. Instead, if you are working on a non-UI thread, you need to use dispatcher to update dispatcherobjects.
Use a scheduler
The dispatcher class provides a channel to the Message pump in WPF, and also provides a mechanism to route the work to be processed by the UI thread. This is necessary to meet the thread Association requirements, but the UI thread is blocked for every job that uses the dispatcher route, so it is very important to make the work done by the dispatcher small and fast. It is best to split the large part of the user interface into smaller discrete parts for dispatcher to execute. Any work that does not need to be completed on the UI thread should be moved to other threads for processing in the background.
Generally, you will use the dispatcher class to send the work item to the UI thread for processing. For example, if you want to use the Thread class to perform some work on a separate thread, you can create a threadstart delegate to perform some work on the new thread, as shown in 3.
ThisCodeExecution failed because no text attribute settings for the statustext control (A textblock) were called on the UI thread. When the code tries to set text on textblock, The textblock class calls its verifyaccess method internally to ensure that the call comes from the UI thread. When it determines that the call is from different threads, an exception is thrown. So how do you use dispatcher to call on the UI thread?
The dispatcher class provides the permission to directly call code on the UI thread. Figure 4 shows how to use the invoke method of dispatcher to call the method named setstatus to change the text attribute of textblock.
The invoke call contains three pieces of information: the priority of the project to be executed, the delegate that describes the job to be executed, and any parameter that is passed to the delegate described in the second parameter. By calling invoke, it will queue the delegate called on the UI thread. You can use the invoke method to ensure that the operation is blocked before the UI thread executes the work.
As an alternative to asynchronous dispatcher, you can use the begininvoke method of dispatcher to asynchronously queue work projects for the UI thread. When you call the begininvoke method, a dispatcheroperation class instance is returned, which contains information about the execution work project, including the current status of the Work Project and the execution result (if the work project has been completed ). Use of the begininvoke method and the dispatcheroperation Class 5 is shown in.
Unlike the typical message pump implementation, dispatcher is a priority-based work item queue. This can achieve better responsiveness, because more important jobs can be executed before less important jobs. The nature of priority can be illustrated by the priority specified in the dispatchpriority enumeration (6 ).
In general, you should always use dispatcherpriority. Normal priority for work items that update the UI appearance (for example, my previous examples. But sometimes different priorities should be used. Three Idle priorities (contextidle, applicationidle, and systemidle) are particularly interesting ). These priorities can be used to specify work items that are executed only when the workload is low.
Backgroundworker
Now that you know how dispatcher works, you may be surprised if you know that it is not used in most cases. In Windows Forms 2.0, Microsoft introduced a class for non-UI thread processing to simplify the development model for user interface developers. This class is called backgroundworker. Figure 7 shows the typical usage of the backgroundworker class.
The backgroundworker component works very well with WPF because it uses the asyncoperationmanager class in the background, and the class uses the synchronizationcontext class to process synchronization. In Windows Forms, asyncoperationmanager submits the windowsformssynchronizationcontext class derived from the synchronizationcontext class. Similarly, in ASP. NET, it works with different derived synchronizationcontext (called aspnetsynchronizationcontext. These synchronizationcontext-Derived classes know how to handle cross-thread synchronization of method calls.
In WPF, this model can be extended using the dispatchersynchronizationcontext class. By using backgroundworker, You can automatically apply dispatcher to call cross-thread method calls. The good news is that you may be familiar with this common mode, so you can continue to use backgroundworker in the new WPF project.
Dispatchertimer
In MicrosoftRegular code execution in. NET Framework is a common task in development, but using a timer in. NET is still confusing. If you. net Framework base class library (BCL) to find the Timer class, then at least three timer classes will be found: system. threading. timer, system. timers. timer and system. windows. forms. timer. Each timer is different. In the msdn magazine, Alex CalvoArticleExplains when to use each of these timer classes (see msdn.microsoft.com/msdnmag/issues/04/02/timersinnet ).
For WPF applications, there is a new type of timer that uses the dispatcher (that is, the dispatchertimer class. Similar to other timers, The dispatchertimer class supports specifying the interval between ticking and the code to run when a timer event is triggered. In figure 8, we can see a fairly common dispatchertimer usage method.
Because the dispatchertimer class is associated with the dispatcher, you can also specify the dispatcherpriority and the dispatcher to be used. The dispatchertimer class uses the "normal" priority as the default priority of the current dispatcher, but you can overwrite these values:
_ Timer = new dispatchertimer (dispatcherpriority. systemidle, form1.dispatcher );
It is worthwhile to plan the workflow to get a faster response to the application. Some initial research work can make the planning more successful. I suggest you browse some of the websites and this article mentioned in the "WPF thread reference" column before you start. They will lay a good foundation for developing faster applications.
--------------------------------------------------
Figure 2
Fire 2 uses verifyaccess and checkaccess
Public class mywpfobject: dispatcherobject {public void dosomething () {verifyaccess (); // do some work} public void dosomethingelse () {If (checkaccess () {// something, only if called // on the right thread }}}
Figure 3
Figure 3 use a non-UI thread to update the UI-the error method
// The work to perform on another threadthreadstart start = delegate (){//... // This will throw an exception // (it's on the wrong thread) statustext. TEXT = "from other thread" ;}; // create the thread and kick it started! New thread (start). Start ();
Figure 4 Figure 4
Update the UI
// The work to perform on another threadthreadstart start = delegate (){//... // sets the text on a textblock control. // This will work as its using the dispatcher. invoke (dispatcherpriority. normal, new action <string> (setstatus), "from other thread") ;}; // create the thread and kick it started! New thread (start). Start ();
Figure 5 Figure 5
Asynchronously update the UI
// The work to perform on another threadthreadstart start = delegate (){//... // This will work as its using the dispatcher dispatcheroperation op = dispatcher. begininvoke (dispatcherpriority. normal, new action <string> (setstatus), "from other thread (async)"); dispatcheroperationstatus status = op. status; while (status! = Dispatcheroperationstatus. completed) {status = op. wait (timespan. frommilliseconds (1000); If (status = dispatcheroperationstatus. aborted) {// alert someone }}; // create the thread and kick it started! New thread (start). Start ();
Figure 6 Figure 6
Dispatchpriority priority (by priority)
Priority |
Description |
Inactive |
The work item is queued but not processed. |
Systemidle |
The work project is scheduled to the UI thread only when the system is idle. This is the lowest priority of the project actually processed. |
Applicationidle |
The work project is scheduled to the UI thread only when the application itself is idle. |
Contextidle |
A work project is scheduled to the UI thread only after a work project with a higher priority is processed. |
Background |
The work project is scheduled to the UI thread only after all layout, rendering, and input projects are processed. |
Input |
Schedule the work item to the UI thread with the same priority as the user input. |
Loaded |
After all the la S and presentations are completed, the work project is scheduled to the UI thread. |
Rendering |
Schedule the work item to the UI thread at the same priority as the rendering engine. |
Databind |
Schedule the work item to the UI thread with the same priority as the data binding. |
Normal |
Schedule the work item to the UI thread with the normal priority. This is the priority for scheduling most application work projects. |
Send |
Schedule a work item to the UI thread with the highest priority. |
Figure 7 Figure 7
Use backgroundworker in WPF
Backgroundworker _ backgroundworker = new backgroundworker ();... // set up the background worker events_backgroundworker.dowork + = _ backgroundworker_dowork; backgroundworker. runworkercompleted + = _ backgroundworker_runworkercompleted; // run the background worker_backgroundworker.runworkerasync (5000 );... // worker methodvoid _ backgroundworker_dowork (Object sender, doworkeventargs e) {// do something}/C Ompleted methodvoid _ backgroundworker_runworkercompleted (Object sender, runworkercompletedeventargs e) {If (E. cancelled) {statustext. Text = "cancelled";} else if (E. Error! = NULL) {statustext. Text = "exception thrown";} else {statustext. Text = "completed ";}}
Figure 8 Figure 8
Running dispatchertimer class
// Create a timer with a normal priority_timer = new dispatchertimer (); // set the interval to 2 seconds_timer.interval = timespan. frommilliseconds (2000); // set the callback to just show the time ticking away // note: we are using a control so this has to run on // The UI thread_timer.tick + = new eventhandler (delegate (Object S, eventargs A) {statustext. TEXT = string. format ("timer ticked: {0} ms", environment. tickcount) ;}); // start the timer_timer.start ();