About. Net delegation and thread -- solving form death

Source: Internet
Author: User
Tags net thread
Document directory
  • Control thread Security Detection
  • Experience BeginInvoke
  • True Meaning of Control. BeginInvoke
  • Control. Invoke, BeginInvoke, and Windows messages
  • Application. DoEvents
  • Try "no false positives"
  • Problem Analysis
  • Final Solution
Introduction: In the previous "create a non-blocking asynchronous call", we have already introduced the compilation steps and implementation principles of asynchronous calls. Asynchronous calling is an important programming method provided by CLR for developers. It is also the key to building high-performance and scalable applications. With the increasing popularity of multi-core CPUs, asynchronous programming allows a very small number of threads to execute many operations. We usually use Asynchronization to perform many complex and time-consuming operations on the compute and IO types to obtain part of the data required for running our applications. After obtaining the data, we need to bind them to the UI for rendering. When the data volume is too large, we will find that the form has become a blank panel. At this point, if you click with the mouse, the title of the form will show "no response", but the UI thread is still working, which is an extremely bad experience for users. If you want to understand the cause (not complicated :) and completely solve the problem, it may be a good option to spend time reading this article. In general, form blocking is divided into two situations. One is to call a time-consuming operation on the UI thread, such as accessing the database. This blocking is caused by the occupation of the UI thread. You can use delegate. ininvoke's asynchronous programming solution; the other is to load a large amount of data in a form, such as adding a large amount of data to ListView, dview and other controls. This article focuses on the latter blocking. This section briefly describes how CLR processes cross-thread UI access. As the basic content, I believe most. NET developers are familiar with it. Readers can skip this section based on their actual situation. In traditional form programming, the control elements in the UI are isolated from other working threads. Every time we access a UI control, it is actually performed in the UI thread. If you try to access the control in other threads, CLR will handle different. NET Framework versions differently. In Framework1.x, CLR allows applications to run in a cross-thread manner. In Framework2.0 and later versions, System. windows. form. control adds the checkforillegalcrossthreadcils attribute, which is a read/write bool constant, marking whether we need to detect the call of a non-UI thread to the Control. If true is specified, when other threads access the UI, the CLR will run a "InvalidOperationException: The Inter-thread operation is invalid, and it is accessed from a thread that is not creating the control ***"; if false, the call of the wrong thread is not captured, and the application is still running. In Framework1.x, the default value is false. What versions will be added to this attribute to constrain our UI? In fact, the official explanation is that when multiple concurrent threads try to read and write the UI, it is easy to cause a deadlock caused by thread contention for resources. Therefore, CLR does not allow non-UI threads to access the control by default. However, we often need to use Asynchronous threads in the form to process some operations, such as IO and Socket communication. In this case, cross-thread UI access is required. In this case, the supplementary solution provided by. NET is Control Invoke and BeginInvoke. For the two methods of Control Invoke and BeginInvoke, we must first have the following understanding:
  1. Control. Invoke, Control. BeginInvoke, delegate. Invoke, and delegate. BeginInvoke are different.
  2. The delegate method in Control. Invoke is executed in the main thread, that is, our UI thread. Control. BeginInvoke indicates that although it has the feature of asynchronous calling (Begin), it is still executed in the UI thread.
  3. If Invoke and BeginInvoke are directly called in the UI thread, when the data volume is too large, the UI will still be suspended.
Many developers can easily associate these two functions asynchronously when they first access them. Some developers may think they are working threads independent of the UI thread. In fact, they are all blinded by the names of these two functions. If we call Control. BeginInvoke directly in the traditional asynchronous method, it is similar to the execution of the synchronous function. The UI thread will still process all the hard operations, resulting in blocking of our applications. Control. the call model of Invoke is clear: the code is executed synchronously in the UI thread in the order of code. Therefore, aside from the interference of the UI elements invoked by the working thread, we can Control the execution. invoke is regarded as synchronization. This article will not introduce it too much. After receiving the asynchronous access, many developers can handle the problem of form spoofing. It is easy to take Control. BeginInvoke as the asynchronous encapsulation of WinForm. So we focus on this method. Experience BeginInvoke As mentioned earlier, in addition to naming, BeginInvoke looks like asynchronous. In fact, many times we call it without the "non-blocking" feature asynchronously, in the following example, I will try to call BeginInvoke. As you can see, I have now created a simple Form with a Lable control lable1 and a Button control btn_Start. Next, start code: privatevoid btn_Start_Click (object sender, EventArgs e)
{
// Store the ID of the UI thread
Int curThreadID = Thread. CurrentThread. ManagedThreadId;

New Thread (ThreadStart) delegate ()
{
PrintThreadLog (curThreadID );
})
. Start ();
}

Privatevoid PrintThreadLog (int mainThreadID)
{
// The identifier of the current thread
// Code block
Int asyncThreadID = Thread. CurrentThread. ManagedThreadId;

// Output the summary of the current thread and compare the result with the reference of the UI thread
// Code block B
Label1.BeginInvoke (MethodInvoker) delegate ()
{
// Indicates the thread identifier of the method in BeginInvoke.
Int curThreadID = Thread. CurrentThread. ManagedThreadId;

Label1.Text = string. Format ("Async Thread ID: {0}, Current Thread ID: {1}, Is UI Thread: {2 }",
AsyncThreadID, curThreadID, curThreadID. Equals (mainThreadID ));
});

// Suspend the current thread for 3 seconds to simulate time-consuming operations
// Code block C
Thread. Sleep (3000 );
} This code accessed the UI in the new thread, so we used the label1.BeginInvoke function. In the new thread, we obtain the thread identifier of the current Working thread and the thread in the BeginInvoke function. Then, compare it with the identifier of the UI thread and output the result to the Label1 control. Finally, we suspend the current worker thread for 3 seconds to simulate some common time-consuming operations. For easy differentiation, we divide this code into three code blocks: A, B, and C.Running result:We can draw the following conclusion: ● the main body of the PrintThreadLog function (code block A and C) is executed in A new thread, and it executes other code not included by BeginInvoke. ● When we call Control. BeginInvoke, the thread scheduling permission is returned to the UI thread. That is to say, the code inside BeginInvoke (code block B) is executed in the UI thread. ● When the UI thread executes the code encapsulated in beginvok, the remaining code (C code block) in the Working thread is simultaneously executed. It is executed in parallel with the UI thread in BeginInvoke and does not interfere with each other. ● Because Thread. Sleep (3000) is an isolated worker Thread outside the UI Thread, the Thread blocking caused by this line of code actually blocks the working Thread and does not affect the UI. The true meaning of Control. BeginInvoke since the delegate function in Control. BeginInvoke is still executed in the UI thread, what does "Asynchronous" mean? The topic is back to the beginning of this article: we have mentioned the concept of "thread Security Detection of controls" above. I believe you are too familiar with this kind of practice of calling Control. BeginInvoke in a working thread. We also mentioned that "CLR does not like the UI elements called by working threads ". Microsoft's determination was so great that the CLR team added the checkforillegalcrossthreadcals, Control. Invoke, Control. BeginInvoke methods in. NET Framework2.0. This is a very major reform, and the CLR team hopes to achieve this effect: If checkforillegalcrossthreadcals = false is not stated; such "insecure" code, you can only use Control. invoke and Control. beginInvoke; as long as the latter two are used, the encapsulated code will be executed in the UI thread no matter whether their context runtime environment is other working threads or UI threads.

Therefore, msdn provides the following explanation for Control. BeginInvoke: It asynchronously executes the specified delegate on the thread where the basic handle of the Control is created. The true meaning of BeginInvoke is that the so-called Asynchronization is relative to the call thread, rather than the Asynchronization of the UI thread. CLR controls. asynchronous functions in BeginInvoke (delegate method) are executed in the UI. If you use a new thread to call BeginInvoke as I did above, the method is asynchronous relative to other functions in the new thread. After all, the method is executed in the UI thread, and the new thread calls back immediately without waiting for the completion of Control. BeginInvoke. Therefore, this background thread fully enjoys the "Asynchronous" benefits and does not block any more, but we cannot see it. Of course, if you execute a time-consuming code in BeginInvoke, whether it is to obtain database data from the remote server, read IO, or load a large number of data in the control, the UI thread is still blocked. Just as the asynchronous working thread of the traditional Delegate. BeginInvoke is taken from the. NET thread pool, the asynchronous working thread of Control. BeginInvoke is the UI thread. Do you understand the differences between the two inininvoke types? Control. Invoke, BeginInvoke, and Windows messages. In fact, the principle of Invoke and BeginInvoke is to convert the called method into messages, and then call RegisterWindowMessage () of Win32Api to send messages to the UI. We use Reflector to see the following code:Control. Invoke:Publicobject Invoke (Delegate method, paramsobject [] args)
{
Using (new MultithreadSafeCallScope ())
{
Returnthis. findexternalingcontrol (). externaledinvoke (this, method, args, true );
}
}Control. BeginInvoke:[EditorBrowsable (EditorBrowsableState. Advanced)]
Public IAsyncResult BeginInvoke (Delegate method, paramsobject [] args)
{
Using (new MultithreadSafeCallScope ())
{
Return (IAsyncResult) this. findexternalingcontrol (). externaledinvoke (this, method, args, false );
}
} In the above code, we can see that the difference between Control. Invoke and BeginInvoke is that when calling MarshaledInvoke, Invoke passes true to the last parameter, while BeginInvoke is false. The MarshaledInvoke structure is as follows: privateobject implements aledinvoke (Control caller, Delegate method, object [] args, bool synchronous). The last parameter synchronous indicates whether synchronization is performed. MarshaledInvoke internally processes this parameter as follows: if (! Synchronous)
{
Return entry;
}
If (! Entry. IsCompleted)
{
This. WaitForWaitHandle (entry. AsyncWaitHandle );
} Therefore, the processing of BeginInvoke is a direct callback, but Invoke continues execution after the asynchronous function is executed. So far, the Invoke and BeginInvoke work is over, and the rest of the work is the UI's processing of the Message, which is executed by the Control's WndProc (ref Message m. What is the impact of Message Processing on our UI? Next let's look at the Application. DoEvents () function. The Application. DoEvents Application. DoEvents () function is an extremely important function in WinForm programming, but in actual programming, most developers seldom call it. If you do not know about this function, you may be confused about the phenomenon of "form suspended" in future programming. When running a Windows form, it creates a new form and waits for processing the event. This form processes all the code associated with the event each time it processes the event. All other events are waiting in the queue. When the code processes events, the application will not respond. For example, if you drag window a to window B, the window B will not be re-drawn. If DoEvents is called in the code, your application can handle other events. For example, if you have a form for adding data to ListBox and add DoEvents to code, the form will be re-drawn when another window is dragged to your form. If you remove DoEvents from the code, your form will not be redrawn until the event handler is executed. Therefore, if we do not process windows messages in the message queue when executing an event in the form, the form will inevitably lose response. As mentioned above, Control. both Invoke and BeginInvoke send messages to the UI, causing the UI to process the messages. Therefore, this provides us with a solution to the false death when the form loads a large amount of data. Solution: Try "no false dead" this time we use the ListView control with extremely high frequency in development to experience an ideal "Asynchronous refresh". A ListView control in the form is named listView1, set View to Detail and add two columnheaders. One Button is named btn_Start. The design View is as follows: Start code: privatereadonlyint Max_Item_Count = 10000;

Privatevoid button#click (object sender, EventArgs e)
{
New Thread (ThreadStart) (delegate ()
{
For (int I = 0; I <Max_Item_Count; I ++)
{
// The "performance trap" caused by Value Type Packing"
ListView1.Invoke (MethodInvoker) delegate ()
{
ListView1.Items. Add (new ListViewItem (newstring []
{I. ToString (), string. Format ("This is No. {0} item", I. ToString ())}));
});
};
}))
. Start ();
} After the code is run, you will see a rolling ListView list. During the loading process, the list adds data at a dazzling speed. At this time, you try to pull the scroll bar, or move the form, you will find that the effect of this time is different from the previous "whiteboard" and "false dead! This is an amazing change.Running process:From my perspective, we can see that during the data loading process of the form, the interface is still drawn, and "false dead" is not displayed ".If the above Code calls Control. BeginInvoke, The program will have some strange phenomenon, think about why?Okay, now, we can finally breathe a sigh of relief. The problem of interface response has been solved and everything is beautiful. However, such a form still exposes two major problems: 1. Compared with traditional loading, the loading speed of the "no dead form" is obviously slowed down. 2. The form flashes sharply during data loading. Problem analysis when we call Control. Invoke, we force the form to process the message, so that the interface gets a response, but also produces some side effects. One of them is that message processing causes the form to be repainted in a loop, and the "blinking" phenomenon is caused by the screen weight painting. developers who have experience with GDI + development should be familiar with it. At the same time, each call to Invoke will enable the UI to process messages, and directly increase the time cost of the control for data processing, resulting in performance problems. I have no solutions for "performance problems" (if you have any opinions, please submit them ). Some controls (ListView and ListBox) have the BeginUpdate and EndUpdate functions, and can temporarily suspend refresh to accelerate performance. However, after all, we have created a scrolling interface, which is unmatched by the "dynamic loading" method of data. For "blinking", I will first explain the cause of the problem. In general, the control is drawn in two steps: to erase the original object and to draw a new object. First, a message is sent in windows, and the notification control erases the original image and then draws it. If you want to paint with a SolidBrush on the control panel, the control will draw content directly on its panel. When the user changes the size of the control, Windows will call a lot of painting recycling operations. When each collection and painting happens, the "Draw" is longer than "erase, this will give users the feeling of "blinking. In the past, to solve such problems, we often need to make complicated processing in Control. WndProc. The. NET Framework provides us with a more elegant solution, that is, double buffering. We can directly call it. Final Solution

  1. Create a Windows component DBListView. cs to inherit from ListView.
  2. Add the following code to the control:
Public DBListView ()
{
// Enable double buffering of controls
SetStyle (ControlStyles. OptimizedDoubleBuffer | ControlStyles. AllPaintingInWmPaint, true );
} Re-generate the project, drag the newly added DBListView from the toolbox to the form, name it dbListView1, and run the following code: privatevoid button#click (object sender, EventArgs e)
{
New Thread (ThreadStart) (delegate ()
{
For (int I = 0; I <Max_Item_Count; I ++)
{
// The "performance trap" caused by Value Type Packing"
DbListView1.Invoke (MethodInvoker) delegate ()
{
DbListView1.Items. Add (new ListViewItem (newstring []
{I. ToString (), string. Format ("This is No. {0} item", I. ToString ())}));
});
};
}))
. Start ();
} Is the problem of "blinking" solved? In our practical applications, blocking caused by data loading is very common. When users do not pay much attention to the interface performance, using the method described in this article to deal with such blocking is a good choice. If we use loading animations like IE8 and thunder, the effect will be better. Download the source code of this Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.