C # resolves forms of suspended animation

Source: Internet
Author: User
Tags net thread

Asynchronous invocation is an important programming tool that the CLR provides to developers, and it is the key to building high-performance, scalable applications. With the increasing popularity of multicore CPUs today, asynchronous programming allows many operations to be performed with very few threads. We often use asynchronous to do many computational, IO-type complex, time-consuming operations to get the part of the data our application needs to run. After getting this data, we need to bind them to render in the UI. When the amount of data is large, we find that the form becomes a blank panel. At this point, if you click with the mouse, the title of the form will appear "unresponsive", while the UI thread is still working, which is an extremely bad experience for the user. If you want to know why and solve the problem completely, it might be a good idea to take the time to finish reading this article.

In general, form blocking is divided into two situations. One is to invoke lengthy operations on the UI thread, such as accessing the database, which is caused by the UI thread being occupied and can be passed through delegate. BeginInvoke asynchronous programming, the other is that forms load large amounts of data, such as adding large amounts of data to controls such as ListView, DataGridView, and so on. This paper mainly discusses the latter kind of blocking.

Theory

This section briefly describes the CLR's handling of cross-threading UI access. As a basic content, believe most. NET developers are not unfamiliar with it, the reader can be skipped here according to the actual situation.

Thread-safe detection of controls

In traditional form programming, the control elements in the UI are isolated from other worker threads, and each time we access a UI control, it is actually done in the UI thread. If you try to access the control in another thread, the CLR has different processing for different versions of the. NET Framework. In framework1.x, the CLR allows applications to run on a cross-thread basis, and in Framework2.0 and later versions, System.Windows.Form.Control adds Checkforillegalcrossthreadcalls properties, which are A read-write bool constant that marks whether we need to detect a call to a control from a non-UI thread. If true, when accessing UI,CLR with another thread, it will run out of a "InvalidOperationException: the thread between the threads is invalid, access it from a thread that is not a control," or if False, the call to the wrong thread is not captured. The application is still running.

In the framework1.x version, this value defaults to False. Q. What does the later version add to this attribute to constrain our UI? In fact, the official explanation is that when there are multiple concurrent threads trying to read and write to the UI, it is easy to create a deadlock caused by thread contention resources. Therefore, the CLR does not allow access to the control as a non-UI thread by default.

However, we often need to use asynchronous threads in the form to handle some operations, such as IO and socket communication. In this case, cross-threading UI access is also necessary, for this. NET to our supplementary program is the control of invoke and BeginInvoke.

Control of the Invoke and the BeginInvoke

For these two methods, first we should have the following knowledge:

1.control.invoke,control.begininvoke and Delegate.invoke,delegate.begininvoke are different.
The delegate method in 2.control.invoke is executed on the main thread, which is our UI thread. While Control.BeginInvoke is named, it has the feature of asynchronous Invocation (Begin), but still executes on the UI thread.
3. If you invoke invoke and BeginInvoke directly in the UI thread, the amount of data is too large to cause the UI to feign death.

There are a lot of developers who, when they first touch these two functions, can easily associate them with async, some think they are separate from the UI thread, and in fact they are blinded by the naming of the two functions. If you call Control.BeginInvoke in a traditional invocation asynchronously, the UI thread will handle all the hard work, causing our application to block, just as the synchronous function executes.

The invocation model of Control.Invoke is very clear: it is executed synchronously in code order in the UI thread, so we can treat Control.Invoke as a synchronization without the interference of the worker thread calling the UI element, this article does not introduce too much.

Many developers in contact with asynchronous, then to deal with the form of suspended animation, it is easy to assume that Control.BeginInvoke as WinForm encapsulated asynchronous. So we focus on this approach.

Experience BeginInvoke

As I said earlier, BeginInvoke, in addition to naming it like async, in fact, many times we call up there is no asynchronous "non-blocking" feature, I use the following example a simple attempt to begininvoke a call.

As you can see, I've now created a primitive form that puts a lable control lable1, a button control Btn_start, and below, start code:

Private Voidbtn_start_click (object sender, EventArgs e)
{
Store the identifier of the UI thread
int curthreadid = Thread.CurrentThread.ManagedThreadId;

New Thread ((ThreadStart) delegate ()
{
Printthreadlog (Curthreadid);
})
. Start ();
}

private void Printthreadlog (int mainthreadid)
{
Identifier of the current thread
A code block
int asyncthreadid = Thread.CurrentThread.ManagedThreadId;

Outputs the main information of the current thread, and the result of reference comparison to the UI thread
B code block
Label1. BeginInvoke ((MethodInvoker) delegate ()
{
Executes the thread identifier of the method within the BeginInvoke
int curthreadid = Thread.CurrentThread.ManagedThreadId;

Label1. Text = string. Format ("Async thread id:{0},current thread Id:{1},isui thread:{2}",
Asyncthreadid, Curthreadid, Curthreadid.equals (Mainthreadid));
});

Suspends the current thread for 3 seconds, simulating time-consuming operations
C code block
Thread.Sleep (3000);
}

This code accesses the UI in the new thread, so we used the Label1. The BeginInvoke function. In the new thread, we get the thread identifier of the current worker thread, and also the thread within the BeginInvoke function. It then Fu the label to the UI thread, outputting the result to the Label1 control. Finally, we suspend the current worker thread for 3 seconds to simulate some common time-consuming operations.

For the sake of distinction, we divide this code into a, B, and c three blocks of code.

Operation Result:

We can get the following conclusions:

The Printthreadlog function body (A, C code block) executes on the new thread, and it executes other code that is not contained by the BeginInvoke.
When we call Control.BeginInvoke, the thread dispatch right returns to the UI thread. That is, the code inside the BeginInvoke (b code block) is executed on the UI thread.
When the UI thread executes code encapsulated in Begininvok, the remaining code within the worker thread (the C code block) occurs at the same time. It is executed in parallel with the UI thread in BeginInvoke and does not interfere with each other.
Because Thread.Sleep (3000) is a worker thread that is isolated outside the UI thread, the thread blocking that this line of code brings actually blocks the worker thread and does not have any effect on the UI.

Control.BeginInvoke the real meaning

Since Control.BeginInvoke's delegate function is still executing within the UI thread, what does this "async" mean? The topic goes back to the beginning of this article: we have already mentioned the "thread-safe detection of controls" concept, and I believe you are too familiar with the idea of invoking control.begininvoke within such a worker thread. We also mentioned that "the CLR does not like worker threads calling UI elements." Microsoft's determination was so great that the CLR team added the Checkforillegalcrossthreadcalls and Control.Invoke, Control.BeginInvoke methods to. NET Framework2.0. This is a rather significant reform, and the CLR team wants to achieve this effect:

If checkforillegalcrossthreadcalls= false is not asserted, such an "unsafe" code, You can only use Control.Invoke and Control.BeginInvoke, and as long as you use the latter two, the code they encapsulate will execute within the UI thread, regardless of whether their context is a different worker or UI thread. So, MSDN gives the Control.BeginInvoke an explanation: Executes the specified delegate asynchronously on the thread that creates the control's underlying handle.

What it really means is that BeginInvoke, called async, is asynchronous relative to the calling thread, not to the UI thread.

The CLR executes the async function in Control.BeginInvoke (Delegatemethod) Inside the UI, and if you call BeginInvoke with a new thread as I did earlier, then the method is asynchronous with respect to the other functions within the new thread. After all, the method executes in the UI thread, and the new thread immediately callbacks without waiting for the control.begininvoke to complete. So, this background thread fully enjoy the "async" benefits, no longer blocking, but we do not see it; Of course, if you execute a time-consuming code within BeginInvoke, whether it's getting database data from a remote server, IO reading, or loading a large chunk of data inside a control, The UI thread is still blocked.

Just as the traditional Delegate.begininvoke asynchronous worker thread is taken from the. NET thread pool, Control.BeginInvoke's asynchronous worker thread is the UI thread.

Now, do you understand the difference between the two types of BeginInvoke?

Control.Invoke, BeginInvoke, and Windows messages

In fact, the principle of Invoke and BeginInvoke is to marshal the called method into a message and then call Win32API's RegisterWindowMessage () to send a message to the UI. Using reflector, we can see the following code:

Control.Invoke:

public Object Invoke (Delegatemethod, params object[] args)
{
using (new Multithreadsafecallscope ())
{
return this. Findmarshalingcontrol (). Marshaledinvoke (This, method, args, true);
}
}

Control.BeginInvoke:

[Editorbrowsable (editorbrowsablestate.advanced)]
Public IAsyncResult BeginInvoke (Delegate method, params object[] args)
{
using (new Multithreadsafecallscope ())
{
Return (IAsyncResult) this. Findmarshalingcontrol (). Marshaledinvoke (this, Method,args, false);
}
}

In the above code we see the difference between Control.Invoke and BeginInvoke, when invoking Marshaledinvoke, invoke passes false to the last argument, and BeginInvoke is true.

The structure of the Marshaledinvoke is this:

Private Objectmarshaledinvoke (Control caller, Delegate method, object[] args, boolsynchronous)

It is clear that the last parameter, synchronous, indicates whether to follow the synchronization process. This parameter is handled internally by the Marshaledinvoke:

if (!synchronous)
{
return entry;
}
if (!entry. iscompleted)
{
This. Waitforwaithandle (entry. AsyncWaitHandle);
}

So, BeginInvoke's handling is a direct callback, and invoke waits for the asynchronous function to execute before continuing.

So far, the work of Invoke and BeginInvoke is over, and the rest is the UI handling of the message, which is performed by the control's WndProc (ref Message m). What effect does message processing have on our UI? Then look at the application.doevents () function.

Application.doevents

The application.doevents () function is a very important function in WinForm programming, but in practical programming, most developers rarely call it. If you have a lack of understanding of this function, it is likely that in the future long-term programming in the "form of suspended animation," the phenomenon of confusion.

When you run a Windows form, it creates a new form, and then the form waits for the event to be processed. The form handles all the code associated with the event each time the event is processed. All other events are waiting in the queue. When the code handles the event, the application does not respond. For example, if you drag a window over window B, the b window will not redraw.

If you call DoEvents in your code, your application can handle other events. For example, if you have a form that adds data to a listbox and adds DoEvents to your code, the form will be redrawn when you drag another window onto your form. If you remove DoEvents from your code, your form will not redraw until the button's Click event handler finishes executing.

Therefore, if we do not process Windows messages in Message Queuing when the form executes events, the form will inevitably lose its response. As already described, Control.Invoke and BeginInvoke send messages to the UI, causing the UI to handle the message, so this gives us the idea of how to suspend the animation when the form loads a lot of data.

Solution Solutions

Try " no suspended animation "

This time we use the very high frequency ListView control in development to experience an ideal "asynchronous flush" with a ListView control named ListView1 in the form and a view set to detail, adding two ColumnHeader A button named Btn_start, the Design view is as follows:

Start code:

Private ReadOnly Intmax_item_count = 10000;

private void Button1_Click (object sender, EventArgs e)
{
New Thread ((ThreadStart) (Delegate ()
{
for (int i = 0; i < Max_item_count; i++)
{
Beware of "performance traps" caused by value type boxing
Listview1.invoke ((MethodInvoker) delegate ()
{
LISTVIEW1.ITEMS.ADD (New ListViewItem (new string[]
{i.tostring (), String. Format ("This is no.{0} item", i.ToString ())});
});
};
}))
. Start ();
}

After the code runs, you will see a list of fast scrolling ListView, in the process of loading, the list at a dizzying speed to add data, when you try to pull the scroll bar, or move the form, will find this effect and the previous "whiteboard", "Suspended animation" different! This is a delightful change.

Run the process:

As can be seen from my, the form in the process of loading data, still draw the interface, and did not appear "suspended animation."

If the above code calls the Control.BeginInvoke, the program will have some strange phenomenon, think is why?

Well, by now, we can finally get a sigh of relief, the interface response problem has been resolved, all good. However, there are two big problems with a form like this:
1. The loading speed of the "No suspended animation form" slows significantly compared to traditional loading.
2. The form has a sharp flicker during data loading.

Problem analysis

When we call Control.Invoke, we force the form to process the message, which results in a response to the interface, as well as some side effects. One of them is that message processing causes the form to redraw in the loop, and the "flashing" phenomenon is caused by the form redraw, and developers with GDI + development experience should be familiar with it. At the same time, each call to invoke causes the UI to process messages and directly increases the time cost of the control to data processing, which results in performance issues.

I don't have a solution for "performance issues" (a friend with my own opinion is welcome). Some controls (ListView, ListBox) have beginupdate and endupdate functions that can temporarily suspend refreshes for faster performance. But after all, we have created a rolling interface, the "dynamic Loading" method of the data is unmatched.

For "flashing", let me explain the cause of the problem first. Typically, the drawing of a control consists of two parts: erasing the original object and drawing a new object. First, Windows sends a message that notifies the control to erase the original image and then draws it. If you want to draw in SolidBrush on the Control Panel, the control draws the content directly on its panel. When the user changes the size of the control, Windows will invoke a lot of drawing recycling operations, and when each recycle and draw occurs, the user will be "blinking" because the "paint" is more delayed than "erase". In the past, in order to solve such problems, we often need to make complex processing in Control.wndproc. and the. NET Framework provides us with a more elegant approach, which is double buffering, which we call it directly.

Final Solution

1. Create a new Windows Component DBListView.cs, and let it inherit from the ListView.
2. Add the following code to the control: public Dblistview ()
{


Double buffering of open controls
SetStyle (Controlstyles.optimizeddoublebuffer | Controlstyles.allpaintinginwmpaint, True);
}

Regenerate the project, and then drag the new build Dblistview from the Toolbox onto the form, named DbListView1, to execute the following code: private void Button1_Click (Objectsender, EventArgs e)
{
New Thread ((ThreadStart) (Delegate ()
{
for (int i = 0; i < Max_item_count; i++)
{
Beware of "performance traps" caused by value type boxing
Dblistview1.invoke ((MethodInvoker) delegate ()
{
DBLISTVIEW1.ITEMS.ADD (New ListViewItem (new string[]
{i.tostring (), String. Format ("This is no.{0} item", i.ToString ())});
});
};
}))
. Start ();
} >

is the "flashing" problem now being resolved?

For DataGridView, it is also the addition of each line,

for (int i = 0; I <Max_Item_Count; i++)
{

Create rows
DataGridViewRow dr = new DataGridViewRow ();
foreach (DataGridViewColumn C in Datagridviewallinfo.columns)
{
Dr. Cells.add (C.celltemplate.clone () as DataGridViewCell);
}
Cumulative number
Dr. Cells[0]. Value = i++;

Try
{
Datagridviewallinfo.invoke ((MethodInvoker) delegate ()
{
DATAGRIDVIEWALLINFO.ROWS.ADD (DR);
});
}
catch (exceptio

C # resolves forms of suspended animation

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.