Monitor message Loops
In the previous article, we discussed that the message loop is the basis for responding to user input, and also mentioned that the time-consuming operation in winform is performed because the time-consuming operation and the message loop are on the same UI thread, as a result, the user's subsequent response cannot be processed, resulting inProgramFalse death. In addition, we also mentioned the wndproc method in form, which is the. Net version of the Message Processing Method in the Win32 era.
So today's articleArticleLet's compile a small program to simulate this time-consuming operation to see if it is as mentioned in the previous article: Temporary interruption caused by time-consuming operations resulting in a message loop cannot respond to subsequent user input.
The program is very simple, it is a simple form, put a button above, there is a thread. Sleep (50*1000) in the button to simulate time-consuming operations:
Public partial class longtimeform: Form
{
Public longtimeform ()
{
Initializecomponent ();
Debug. listeners. Add (New leletracelistener ());
}
Private void btnlongtime_click (Object sender, eventargs E)
{
Thread. Sleep (50*1000 );
}
// Since wndproc is the. NET version of the method for processing messages in Win32, we should be able to monitor the "messages" operated by all users here"
Protected override void wndproc (ref message m)
{
Debug. writeline (M. msg. tostring ());
Base. wndproc (ref m );
}
}
Wndproc is a virtual method. We can override it, so let's see if this method can still receive other user input after the user clicks the "time-consuming operation" button.
Tips
When developing a winform program, we often change the "Windows application" project type in the project attribute to "console application" to facilitate display of some operation logs of the program ", in this way, after the program is started, in addition to the display window in vitro, a console is displayed, and the program is displayed through debug in the console. write and other output logs. When the product is released, we can modify the project properties back to "Windows application ".
Start the program, move the mouse over the form, and many numbers will be displayed on the console. These are the messages that are retrieved from the message queue by the message loop (the number is the type of the message, defined in a Windows header file), and then passed to the wndproc method for processing. The console can also output numbers to indicate that the message loop is still "fluent ". When we click the "time-consuming operation" button, we find that the form is "dead" at this time and cannot accept any user operation, regardless of how the mouse slides or clicks on the form, there is no output in the console. Well, the message loop is blocked. Thread. Sleep is 50 s. After 50 s, the program has been active again, and there is a steady stream of output on the console.
Although we use examples to prove that what we mentioned in the previous article seems to be correct, can we do nothing about this time-consuming operation? No, we have multithreading and asynchronous operations. Let's take a look at how to use asynchronous operations to handle such time-consuming operations.
Use begininvoke in the delegate for asynchronous operations
Do you still remember the entrusted begininvoke method and endinvoke method? Today, we will use these two methods to perform an asynchronous operation (if you see this inxxx and endxxx pair in. net, it means you can perform asynchronous operations ).
After you define a delegate, the compiler automatically generates a class for you and provides you with a begininvoke method and an endinvoke method in this class, the implementation of these two methods is provided by CLR, and this begininvoke and endinvoke only play a packaging role. Let's take a look at changing the time-consuming operations above to asynchronousCodeRight:
Public partial class longtimeform: Form
{
Public longtimeform ()
{
Initializecomponent ();
Debug. listeners. Add (New leletracelistener ());
}
Private void longtimemethod ()
{
Thread. Sleep (50*1000 );
}
Private void btnlongtime_click (Object sender, eventargs E)
{
// We will not define new delegates here, And. Net defines a series of universal delegates for us.
Action longtimeaction = new action (longtimemethod );
Longtimeaction. begininvoke (null, null );
}
Protected override void wndproc (ref message m)
{
Debug. writeline (M. msg. tostring ());
Base. wndproc (ref m );
}
}
Run the program again. Wow, even though the "time-consuming operation" button is clicked, the message loop continues, and the interface is not suspended. Well, this is the user experience we want (of course, we should also give the user some prompts that time-consuming operations are in progress, please do not close the window ).
Next, let's take a look at how this asynchronous operation is implemented. First, set the breakpoint in the longtimemethod method, click the "time-consuming operation" button, and wait until the breakpoint is hit. After the breakpoint is hit, select "debug"> "Windows"> "Threads" in Visual Studio to open the thread window. here we can see a similar image:
The arrow indicates the worker thread of the longtimemethod method being executed. From here, we can see that the main method is in the main thread, it seems that begininvoke selects an idle thread from the thread pool to execute our time-consuming operations. I mentioned this because I am often asked: What is the difference between Asynchronization and multithreading? In fact, Asynchronization is the purpose, while multithreading is the method to achieve this purpose. Asynchronous means that after a initiates an operation (usually time-consuming operations, there is no need for asynchronous operations if the operation is not time-consuming), a can continue to handle its own affairs, you don't have to wait for this time-consuming operation to return .. This asynchronous programming model in. Net simplifies multi-threaded programming. We can do an asynchronous operation without even having to worry about the Thread class.
Go back
In fact, the time-consuming operations demonstrated above are "never returning" operations (equivalent to one-way operations in WCF), that is, after I initiate this operation, I don't have to worry about it anymore, I don't even care about the result of its operation. But most of the time we need this operation: After the execution is complete, we will return to update the UI, for example, telling the user that I have finished or displayed the execution result. In this case, we need to consider several asynchronous calling methods. If we want to obtain the result from an asynchronous operation, we have to call the endinvoke method. what means do we use to obtain the signal of Asynchronous Operation completion? If the asynchronous operation is not completed, the endinvoke method will be called directly, which will block the execution until the asynchronous operation is completed.
Before continuing the discussion, let's take a look at the returned values of begininvoke:
1: Public interface iasyncresult
2 :{
3: Object asyncstate {Get ;}
4:
5: waithandle asyncwaithandle {Get ;}
6:
7: bool completedsynchronously {Get ;}
8:
9: bool iscompleted {Get ;}
10 :}
Based on the results returned by begininvoke, we have two ways to call asynchronous operations:
Round Robin
The iscompleted attribute of iasyncresult returns true after the asynchronous operation is complete; otherwise, false is returned. Then we can use a loop to continuously access the iscompleted attribute. When iscompleted is true, we can call the endinvoke method:
1: Public partial class longtimeform: Form
2 :{
3: Public longtimeform ()
4 :{
5: initializecomponent ();
6: Debug. listeners. Add (New leletracelistener ());
7 :}
8:
9: Private int longtimemethod ()
10 :{
11: thread. Sleep (50*1000 );
12: return 10;
13 :}
14:
15: Private void btnlongtime_click (Object sender, eventargs E)
16 :{
17: // we will not define a new delegate here.. Net defines a string of universal delegate usage for us.
18: func <int> longtimeaction = new func <int> (longtimemethod );
19: iasyncresult asynresult = longtimeaction. begininvoke (null, null );
20:
21: // you can do other things.
22: While (! Asynresult. iscompleted)
23 :{
24:
25 :}
26: int result = longtimeaction. endinvoke (asynresult );
27:
28 :}
29: protected override void wndproc (ref message m)
30 :{
31: Debug. writeline (M. msg. tostring ());
32: Base. wndproc (ref m );
33 :}
34 :}
Waitone
In iasyncresult, there is also an asyncwaithandle attribute, which is a waithandle type attribute. This object has a waitone method and can accept a timeout time, which will wait for the length specified by the timeout time:
1: Private int longtimemethod ()
2 :{
3: thread. Sleep (50*1000 );
4: return 10;
5 :}
6: Private void btnlongtime_click (Object sender, eventargs E)
7 :{
8: func <int> longtimeaction = new func <int> (longtimemethod );
9: iasyncresult asynresult = longtimeaction. begininvoke (null, null );
10:
11: // you can continue to handle other tasks.
12:
13: If (asynresult. asyncwaithandle. waitone (10000, true ))
14 :{
15: int result = longtimeaction. endinvoke (asynresult );
16 :}
17 :}
The code above means that after the time-consuming asynchronous call, we can continue to do our own thing, and then wait for a signal after doing our own thing. What signal? This is the signal of time-consuming operations. Don't let me wait too long. After a long time, I will be impatient (I can only wait 10 seconds ). Dizzy, the above time-consuming operation will take 50 seconds, you will wait 10 seconds, this is not to play with me (10 seconds passed, this waitone will no longer wait, the thread will continue to execute ).
Callback
In fact, whether it is using the polling method or waitone to wait for a semaphore, or to wait. Waiting is a very annoying thing .. Net has taken this into consideration, and we have prepared a callback method: You can continue to do your work after asynchronous calling. After you finish executing the callback, you will be OK.
1: Private void btnlongtime_click (Object sender, eventargs E)
2 :{
3: func <int> longtimeaction = new func <int> (longtimemethod );
4: // a Lambda expression is used here, saving a lot of effort.
5: iasyncresult asynresult = longtimeaction. begininvoke (result) => {
6: int ret = longtimeaction. endinvoke (result );
7:}, null );
8 :}
After the asynchronous operation is complete, a callback method represented by a Lambda expression in the above code is executed. Here, endinvoke is called to obtain the result of the time-consuming operation. Here, let's look at why Lambda is used. If Lambda is not used, there is no need for an anonymous method (no matter what you use, it actually forms a closure). What do you do? For your own consideration.
Update the UI
The above four asynchronous calling methods are as follows. Polling, waiting, and callback. In fact, all the results of time-consuming operations let the code "eat. Generally, the time-consuming operations must be completed, such as updating the UI. Let's take a look at how to update the UI.
When you run this program, when the time-consuming operation is over, click it and an exception occurs:
Ah? Why? Why not. You cannot access the control from the thread that is not creating the control. What should we do? It seems that our asynchronous operations have to be improved.
Http://www.cnblogs.com/yuyijq/archive/2009/11/16/1603621.html