PrefaceMost of this article comes from
MikeperetzAsynchronous Method invocation and some of my personal experience, I hope to help you. The original English literature can be found in the CodeProject.
Introduction
This article describes the implementation mechanism of asynchronous calls and how to invoke asynchronous methods. Most. NET developers, after delegate, Thread, asynchronousinvocation, often confuse and misuse the above concepts. In fact, the above concepts are. The core support for parallel programming in the NET2.0 version, based on conceptual error awareness, is likely to lead to the inability to optimize our programs with the features of asynchronous calls in real programming, such as the form "suspended animation" caused by large data loads. In fact, this is not a difficult problem, the article will be in a layer-by-step, cobwebs-depth approach to the learning of asynchronous programming.
synchronous vs. asynchronous
Most people do not like to read a lot of text descriptions, but prefer to read the code directly, so we will describe the synchronous and asynchronous invocations in the form of code.
Synchronous Method Invocation
Suppose we have a function whose function is to suspend the current thread for 3 seconds.
Staticvoid Sleep ()
{
Thread.Sleep (3000);
}
Usually, when your program calls sleep, it waits for 3 seconds, and in that 3 seconds you can't do anything else. After 3 seconds, control is handed back to the calling thread (usually your main thread, the UI thread of the WinForm program). This type of invocation is called synchronization, and the order of this call is as follows:
Call Sleep ();
Sleep () in execution;
Sleep () execution is complete and control is returned to the calling thread.
We call the sleep () function again, and the difference is that we are going to do this call based on the delegate. In general, in order to bind a function to a delegate, we need to define a delegate that is exactly the same as the return type of the function and the parameter value, which is somewhat troublesome. But the. NET internals have defined some delegates for us, such as MethodInvoker, which is a non-return, parameterless delegate signature, which is equivalent to customizing a delegate:
Publicdelegatevoid Simplehandler ();
Execute the following code:
MethodInvoker invoker =new MethodInvoker (Sleep);
Invoker. Invoke ();
We use the delegate, but it's still the way of synchronization. The main thread still waits for 3 seconds to suspend and then gets a response.
Note: The Delegate.invoke is synchronous.
Asynchronous Method Invocation
How to call the Sleep () method at the same time, so that the main thread can not wait for the completion of sleep (), has been able to get the corresponding? This is important, which means that the main thread is still non-blocking while the function is executing. In a program with a background service type, a non-blocking state means that the application service can accept another task while waiting for one task, and in a traditional WinForm program, it means that the main thread (i.e. the UI thread) can still respond to the user's actions, avoiding "suspended animation". We continue to call the sleep () function, but this time we're going to introduce BeginInvoke.
MethodInvoker invoker =new MethodInvoker (Sleep);
Invoker. BeginInvoke (null, NULL);
Note BeginInvoke This line of code, which executes the function body called by the delegate. At the same time, the thread that calls the BeginInvoke method (hereinafter referred to as the calling thread) immediately responds without waiting for the sleep () function to complete.
The above code is asynchronous, and the calling thread can handle the other work at the same time as the calling function, but it is not enough that we still don't know when the call to the sleep () function will end, which is the problem that will be resolved later.
BeginInvoke can completely replace invoke in an asynchronous way, and we don't have to worry about the case where the function contains parameters, which are described below.
Note: The Delegate.begininvoke is asynchronous. If you're going to perform a task, but don't care when it's done, we can use BeginInvoke, which does not cause blocking of the calling thread.
for asynchronous calls, the. What exactly does it do inside the net?
Once you use. NET completes an asynchronous call that requires a thread to handle the asynchronous work content (hereinafter referred to as an asynchronous thread), and the async thread cannot be the current calling thread, because that still causes the calling thread to block, as is the case with synchronization. In fact. NET joins all asynchronous request queues to the thread pool, with threads Cheng all asynchronous requests. The thread pool does not seem to be too deep to understand, but we still need to focus on the following points:
The asynchronous invocation of Sleep () executes within a separate thread, which is derived from. NET thread pool.
. NET thread pool contains 25 threads by default, you can change the upper limit of this value, each asynchronous call will use one of these threads to execute, but we can not control which thread is used specifically.
The thread pool has a maximum number of threads, and once all the threads are busy, the new asynchronous call will be placed in the wait queue until the thread pool has a new available thread, so for a large number of asynchronous requests, we need to pay attention to the number of requests that might otherwise have a performance impact.
simply understand the thread pool
To expose the thread pool's upper limit, we modify the sleep () function to extend the thread hang time to 30s. In the running output of the code, we need to focus on the following:
The number of available threads for the line Cheng.
Whether the asynchronous thread is from the thread pool.
The thread-managed ID value.
As already mentioned above. The net thread pool contains 25 threads by default, so we call 30 async methods consecutively, so that after the 25th call, we can see what happens inside the thread pool.
Privatevoid Sleep ()
{
int intavailablethreads, intavailableioasynthreds;
Get the number of threads available for thread Cheng, we only care about the first parameter
Threadpool.getavailablethreads (out Intavailablethreads,
Out intavailableioasynthreds);
Thread Information
String strmessage =
String.Format ("Thread pool threads: {0}, thread managed Id:{1}, number of available threads: {2}",
Thread.CurrentThread.IsThreadPoolThread.ToString (),
Thread.CurrentThread.GetHashCode (),
Intavailablethreads);
Console.WriteLine (strmessage);
Thread.Sleep (30000);
}
Privatevoid Callasyncsleep30times ()
{
To create a delegate object that contains the sleep function
MethodInvoker invoker =new MethodInvoker (Sleep);
for (int i =0; I <30; i++)
{
Call the sleep function 30 times in asynchronous form
Invoker. BeginInvoke (null, NULL);
}
}
Output Result:
For the output results, we can summarize the following:
All of the asynchronous threads come from. NET thread pool.
Each time an asynchronous call is executed, a new thread is generated, and the number of available threads decreases.
After an asynchronous call has been performed 25 times, there are no more idle threads in the thread pool. At this point, the application waits for the idle thread to produce.
Once an idle thread is generated within the thread pool, it is immediately assigned to the asynchronous task waiting queue, and then the thread pool still does not have an idle thread, and the application main thread enters the pending state to continue waiting for the idle thread, so that the call continues until the asynchronous call is executed 30 times.
For the above results, we can summarize the following for asynchronous calls:
Each asynchronous call is executed in a new thread, and this thread comes from. NET thread pool.
The thread pool has its own execution cap, and if you want to execute multiple long-time asynchronous calls, the thread pool is likely to enter a "threading hunger" state to wait for the available threads to produce.
BeginInvoke and EndInvoke
We already know how to execute an asynchronous call without blocking the calling thread, but we have no way of knowing the result of the asynchronous invocation and when it is done. In order to solve the above problems, we can use EndInvoke. EndInvoke will cause the thread to block until the asynchronous method execution is complete. Therefore, calling EndInvoke after calling BeginInvoke is almost entirely equivalent to executing your function in blocking mode (EndInvoke suspends the calling thread until the asynchronous function executes). But. NET is how to bind BeginInvoke and EndInvoke? The answer is IAsyncResult. Each time we use BeginInvoke, the return value is the IAsyncResult type, and it is. NET tracks key values for asynchronous calls. What is the result after each asynchronous call? If you want to know the results of the implementation, IAsyncResult can be considered a label. With this tag, you can see when an asynchronous call is executed, and more importantly, it can hold the parameters of the asynchronous call and solve the asynchronous function context problem.
We now use a few examples to understand IAsyncResult. If you don't know much about it before, then you need to be patient with it, because this type of invocation is. NET asynchronous calls.
Privatevoid Sleeponesecond ()
{
The current thread hangs for 1 seconds
Thread.Sleep (1000);
}
Privatevoid Usingendinvoke ()
{
Create a delegate that points to Sleeponesecond
MethodInvoker invoker =new MethodInvoker (Sleeponesecond);
Start executing Sleeponesecond, but this time asynchronously calls us to pass some parameters
Observe the second parameter of Delegate.begininvoke ()
IAsyncResult tag = invoker. BeginInvoke (NULL, "passing some state");
The application will cause blocking here until the Sleeponesecond execution completes
Invoker. EndInvoke (tag);
EndInvoke execution complete, get the parameter content passed before
String strstate = (string) tag. asyncstate;
Console.WriteLine ("EndInvoke pass parameter" + tag. Asyncstate.tostring ());
}
Output Result:
Back to the "form Dynamic Update" issue that was originally mentioned in the article, if you run the above code in a WinForm program, you will find that the form is still in "suspended animation." In this case, you might get confused: before you say that async functions are executing in a thread pool, you can be sure that the execution of an async function does not cause the UI thread to be busy, but why is the form still stuck in "suspended animation"? The problem is EndInvoke. The role of EndInvoke at this time is the "thread lock", which acts as a scheduler between the calling thread and the asynchronous thread, and sometimes the calling thread needs to use the result of an asynchronous function, so the dispatch thread needs to wait until the result is executed before it can continue to run. EndInvoke is responsible for monitoring the execution of asynchronous functions and suspending the calling thread on the one hand.
Therefore, in the win form environment, the "suspended animation" of the UI thread is not caused by the busy thread, but is EndInvoke "benign" temporary blockade, just to wait for the completion of the asynchronous function.
We can summarize the following for EndInvoke:
When the EndInvoke is executed, the calling thread goes into a pending state until the asynchronous function finishes executing.
Using EndInvoke allows the application to know when an asynchronous function has finished executing.
If this is called "async", you must think that this "async" is the name, although it is known when the asynchronous function is completed, also get the value of the asynchronous function, but our calling thread will still wait for the function to complete, in the waiting process block, in fact, the same as synchronous call.
How do I catch an exception?
Now we are slightly complicating the problem, considering the case where an asynchronous function throws an exception. We need to know where to catch the anomaly, BeginInvoke, or EndInvoke? Is it even possible to catch an exception? The answer is EndInvoke. BeginInvoke's job is just to start the thread pool. For the execution of an asynchronous function, EndInvoke needs to handle all the information that the function performs, including the exception it produces.
Privatevoid Sleeponesecond ()
{
Thread.Sleep (3000);
Thrownew Exception ("Here are an Async Function Exception");
}
Privatevoid Usingendinvoke ()
{
Create a delegate that points to Sleeponesecond
MethodInvoker invoker =new MethodInvoker (Sleeponesecond);
Start executing Sleeponesecond, but this time asynchronously calls us to pass some parameters
Observe the second parameter of Delegate.begininvoke ()
IAsyncResult tag = invoker. BeginInvoke (NULL, "passing some state");
Try
{
The application will cause blocking here until the Sleeponesecond execution completes
Invoker. EndInvoke (tag);
}
catch (Exception ex)
{
Exceptions can be caught here
MessageBox.Show (ex. Message);
}
EndInvoke execution complete, get the parameter content passed before
String strstate = (string) tag. asyncstate;
Console.WriteLine ("EndInvoke pass parameter" + tag. Asyncstate.tostring ());
}
After executing the above code, you will find that the exception is caught only when using EndInvoke, otherwise the exception will be lost. It is important to note that running a program directly in the compiler does not produce a catch exception, only in the debug, release environment, the exception will be in the form of a dialog box directly pop-up.
passing parameters to a function
Now let's change the async function so that it receives some parameters.
privatestring funcwithparameters (int param1, string param2, ArrayList param3)
{
We're here to change the value of the parameter
param1 = 100;
param2 = "Hello";
Param3 =new ArrayList ();
Return "Thank you for reading me";
}
Here we use BeginInvoke and EndInvoke to invoke this function, first we create a delegate signature that matches the function.
publicdelegatestring delegatewithparameters (int param1, string param2, ArrayList param3);
We can consider BeginInvoke and EndInvoke as special functions that divide an asynchronous function into two parts. BeginInvoke receives incoming parameters through its own two parameter values (one AsyncCallback delegate, an Object object), EndInvoke is used to calculate outgoing parameters (parameters marked out or ref) and function return values.
Now we go back to our functions funcwithparameters,param1, param2, param3 are incoming values, and they are also treated as arguments to the BeginInvoke, and the return value of the function is a string type. It will be used as the return type of the EndInvoke. The cool thing is that the compiler can automatically generate the correct parameters and return value types for BeginInvoke and EndInvoke through the delegate type.
Note that we have assigned new values to the parameters in the async function, so that we can verify what values the parameters will come out after calling the Async function ...
Privatevoid Callfuncwithparameters ()
{
Create several parameters
String strparam = "Param1";
int intvalue = 100;
ArrayList list =new ArrayList ();
List. ADD ("Item1");
To create a delegate object
Delegatewithparameters Delfoo =
New Delegatewithparameters (funcwithparameters);
Calling asynchronous functions
IAsyncResult tag =
Delfoo.begininvoke (Intvalue, strparam, list, NULL, NULL);
Normally the calling thread will get a response immediately
So you can do some other processing here.
Execute EndInvoke to get the return value
String strresult = Delfoo.endinvoke (tag);
Trace.WriteLine ("param1:" + intvalue);
Trace.WriteLine ("param2:" + strparam);
Trace.WriteLine ("ArrayList count:" + list. Count);
}
Our asynchronous function changes the parameters without affecting their outgoing values, and now we turn the ArrayList into a ref parameter to see what changes will be brought to EndInvoke.
Publicdelegatestring delegatewithparameters (Outint param1, string param2, ref ArrayList PARAM3);
Privatestring funcwithparameters (Outint param1, string param2, ref ArrayList PARAM3)
{
We're here to change the value of the parameter
param1 = 300;
param2 = "Hello";
Param3 =new ArrayList ();
Return "Thank you for reading me";
}
Privatevoid Callfuncwithparameters ()
{
Create several parameters
String strparam = "Param1";
int intvalue = 100;
ArrayList list =new ArrayList ();
List. ADD ("Item1");
To create a delegate object
Delegatewithparameters Delfoo =
New Delegatewithparameters (funcwithparameters);
Calling asynchronous functions
IAsyncResult tag =
Delfoo.begininvoke (out Intvalue, strparam, ref list, NULL, NULL);
Normally the calling thread will get a response immediately
So you can do some other processing here.
Call EndInvoke and find that intvalue and list can be passed as arguments.
is because they can be updated by asynchronous functions
String strresult = Delfoo.endinvoke (out intvalue, ref list, tag);
Trace.WriteLine ("param1:" + intvalue);
Trace.WriteLine ("param2:" + strparam);
Trace.WriteLine ("ArrayList count:" + list. Count);
}
PARAM2 does not change because it is an input parameter, param1 as an output parameter, the value that is updated to 300;arraylist has been reassigned, and we can find that its reference is directed to an empty element of the ArrayList object (the initial reference is lost). With the above examples, we should be able to understand how the parameters are passed between BeginInvoke and EndInvoke. Now let's try to complete an asynchronous call in a non-blocking mode, which is a play!
about IAsyncResult
Now we know that EndInvoke can provide us with outgoing parameters and updated ref parameters, or we can export exception information from an async function. For example, we use BeginInvoke to invoke the asynchronous function sleep, which begins execution. After calling EndInvoke, you can get sleep when execution is complete. But what if we were to call EndInvoke after 20 minutes of sleep execution? EndInvoke will still provide us with outgoing values and exceptions in async (if an exception is generated), where exactly is this information stored? How does EndInvoke still be able to invoke these return values after the function has been executed for so long? The answer lies in the IAsyncResult object. EndInvoke each time it executes, it invokes an object as a parameter, which includes the following information:
Whether the Async function has completed
A reference to the delegate that called the BeginInvoke method
All outgoing parameters and their values
All of the ref parameters and their updated values
return value of the function
Exceptions generated by asynchronous functions
IAsyncResult looks empty because it is just an interface that contains several properties, and in fact it is a System.Runtime.Remoting.Messaging.AsyncResult object.
If we monitor the state of the tag while the compiler is running, we find that the AsyncResult object contains an object of type System.Runtime.Remoting.Messaging.ReturnMessage. By opening it, you will find the execution information of all the asynchronous functions contained in this tag!
using callback delegation: Hollywood principles "Don't touch me, I'll call you."
So far, we need to know how to pass parameters, how to catch exceptions, and to understand that our async method is actually executing in a specific thread object in the inline pool. The only thing that is not involved is how to be notified when an asynchronous function completes execution. After all, blocking the calling thread from waiting for the function to end is always passable. To achieve this, we must provide a callback delegate for the BeginInvoke function. Take a look at two functions:
private void Callsleepwithoutoutandrefparameterwithcallback ()
{
Create several parameters
String strparam = "Param1";
int intvalue = 100;
ArrayList list = new ArrayList ();
List. ADD ("Item1");
To create a delegate object
Delegatewithparameters Delsleep =
New Delegatewithparameters (funcwithparameters);
Delsleep.begininvoke (out Intvalue, strparam, ref list, new AsyncCallback (CallBack), null);
}
private void CallBack (IAsyncResult tag)
{
Our int parameter is marked out, so the initial value cannot be defined here
int intoutputvalue;
ArrayList list = null;
IAsyncResult is actually the AsyncResult object,
It is also possible to obtain a delegate object that is used to invoke the function
AsyncResult result = (AsyncResult) tag;
Get commissioned
Delegatewithparameters del = (delegatewithparameters) result. AsyncDelegate;
After we have obtained the Commission, we need to execute the EndInvoke on it.
This allows you to get the results of the execution in the function.
String strreturnvalue = Del. EndInvoke (out Intoutputvalue, ref list, tag);
Trace.WriteLine (Strreturnvalue);
}
Here, we pass the callback callback function to BeginInvoke. Such. NET can call the callback function after Funcwithparameters () has finished executing. Before we learned that we had to use EndInvoke to get the results of the function execution, note that in order to use EndInvoke, we used some special operations to get the delegate object.
IAsyncResult is actually the AsyncResult object,
It is also possible to obtain a delegate object that is used to invoke the function
AsyncResult result = (AsyncResult) tag;
Get commissioned
Delegatewithparameters del = (delegatewithparameters) result. AsyncDelegate;
last question: What thread is the callback function executing on?
All in all, the callback function (callback function) is the. NET implementation of the call through our delegate object. We might want to get a clearer picture: What exactly does the callback function do on that thread? To achieve this goal: we add line logs to the function.
private string funcwithparameters (out int param1, string param2, ref ArrayList PARAM3)
{
Log Thread Information
Trace.WriteLine ("In Funcwithparameters:thread Pool?")
+ Thread.CurrentThread.IsThreadPoolThread.ToString () +
"Thread Id:" + Thread.CurrentThread.GetHashCode ());
Suspend seconds to simulate a thread doing a long-time task here
Thread.Sleep (4000);
We're here to change the value of the parameter
param1 = 300;
param2 = "Hello";
PARAM3 = new ArrayList ();
There's a lot of time-consuming work to do here.
Thread.Sleep (3000);
Return "Thank you for reading me";
}
private void CallBack (IAsyncResult tag)
{
What thread is the callback function executing on?
Trace.WriteLine ("In Callback:thread Pool?")
+ Thread.CurrentThread.IsThreadPoolThread.ToString () +
"Thread Id:" + Thread.CurrentThread.GetHashCode ());
Our int parameter is marked out, so the initial value cannot be defined here
int intoutputvalue;
ArrayList list = null;
IAsyncResult is actually the AsyncResult object,
It is also possible to obtain a delegate object that is used to invoke the function
AsyncResult result = (AsyncResult) tag;
Get commissioned
Delegatewithparameters del = (delegatewithparameters) result. AsyncDelegate;
After we have obtained the Commission, we need to execute the EndInvoke on it.
This allows you to get the results of the execution in the function.
String strreturnvalue = Del. EndInvoke (out Intoutputvalue, ref list, tag);
Trace.WriteLine (Strreturnvalue);
}
I put the callsleepwithoutoutandrefparameterwithcallback () function in a click event of a Form button, and three consecutive clicks, you get the result:
Note that the Funcwithparameter function is executed 3 times in succession, and they are executed sequentially on separate threads, and these threads come from the thread pool. Their respective callback functions are also executed in the same thread as the Funcwithparameter. After thread 11 executes funcwithparameter,3 seconds, its callback function is also executed in thread 11, and Threads 12, 13 are also the same. In this way, we can assume that a callback function is actually a continuation of an asynchronous function.
Why do you do this? Perhaps because of this we do not have to spend too much thread pool threads, to achieve the effect of thread reuse, by executing on the same thread, you can also avoid the transfer of the context between different threads of the loss problem.
So far, we're going to execute an asynchronous function in the form, and we'll get an asynchronous call that doesn't block the main thread at all, and that's what we want!
Application Scenario Simulation
Now that we understand the use and features of BeginInvoke, EndInvoke, and callback, how can we apply them to our win form program, so that data acquisition no longer blocks the UI thread, implementing the effect of loading data asynchronously? We will now explain it through a concrete example.
Scenario Description : Queries the system's operation log from the database and loads it into the listbox control on the front-end.
Requirements : The process of querying a database is a time-complex job, but our form does not allow "suspended animation" when executing queries.
private void Button1_Click (object sender, EventArgs e)
{
Getlogdelegate Getlogdel = new Getlogdelegate (getlogs);
Getlogdel.begininvoke (New AsyncCallback (Logtablecallback), null);
}
Public delegate DataTable Getlogdelegate ();
<summary>
Gets the operation log from the database, which takes a long time,
And the amount of returned data is large, logging may exceed million.
</summary>
<returns></returns>
Private DataTable Getlogs ()
{
String sql = "SELECT * FROM * * *";
DataSet ds = new DataSet ();
using (OracleConnection cn = new OracleConnection (connectionString))
{
cn. Open ();
OracleCommand cmd = new OracleCommand (SQL, CN);
OracleDataAdapter adapter = new OracleDataAdapter (cmd);
Adapter. Fill (DS);
}
Return DS. Tables[0];
}
<summary>
Binds a log to the ListBox control.
</summary>
<param name= "Tag" ></param>
private void Logtablecallback (IAsyncResult tag)
{
AsyncResult result = (AsyncResult) tag;
Getlogdelegate del = (getlogdelegate) result. AsyncDelegate;
DataTable logtable = Del. EndInvoke (tag);
if (this.listBox1.InvokeRequired)
{
This.listBox1.Invoke (New MethodInvoker (Delegate ()
{
Bindlog (logtable);
}));
}
Else
{
Bindlog (logtable);
}
}
private void Bindlog (DataTable logtable)
{
This.listBox1.DataSource = logtable;
}
The above code, when fetching data, will not cause any UI thread to block.
Summary:
The main purpose of this paper is to summarize the method of calling function in non-blocking mode, we should understand the following conclusions;
Delegate will generate the correct parameters for BeginInvoke and EndInvoke calls, and all outgoing parameters, return values, and exceptions can be obtained in EndInvoke.
Don't forget that BeginInvoke is a thread taken from a thread pool, and be careful to prevent the number of asynchronous tasks from exceeding the thread pool's upper-bound value.
The callback delegate represents a callback to the asynchronous task, which frees us from the blockage.
As of now, the UI thread will no longer block when it processes asynchronous work, and it will only block if the UI specifics are updated.
problem
We will find that once the amount of data is large, our UI thread will still have "suspended animation" when loading the data into the control. This is normal because we only guarantee the independence of getting data and the UI thread, and there is no guarantee that the update UI will bring the thread busy problem, "suspended animation" is the UI thread busy with a user experience, how to avoid this situation, continue to describe below.
Talking about. NET delegates and threads--creating non-blocking asynchronous calls