IAsyncResult
Now we know that EndInvoke can provide outgoing parameters and updated ref parameters, or export exception information in asynchronous functions to us. For example, we use BeginInvoke to call the asynchronous function Sleep and it starts execution. Call EndInvoke to obtain the execution time when Sleep is finished. But what if we call EndInvoke 20 minutes after the Sleep execution is complete? EndInvoke will still provide us with outgoing values and exceptions in asynchronous mode (if exceptions occur), where is the information stored? How can EndInvoke still call these return values after the function has been executed for so long? The answer lies in the IAsyncResult object. Each time an EndInvoke is executed, it calls this object as a parameter, which includes the following information:
● Whether asynchronous functions have been completed
● Reference to the delegate that calls the inininvoke Method
● All outgoing parameters and their values
● All ref parameters and their updated values
● Function return value
● Exceptions caused by asynchronous Functions
IAsyncResult looks empty because it is only an interface that contains several attributes. In fact, it is a System. Runtime. Remoting. Messaging. AsyncResult object.
If we monitor the tag status during the compiler running, we will find that the AsyncResult object contains objects of the System. Runtime. Remoting. Messaging. ReturnMessage type. Click it and you will find the execution information of all asynchronous functions contained in this tag!
Use Callback to delegate: Hollywood principles "don't contact me, I will contact you"
So far, we need to know how to pass parameters and capture exceptions. Our Asynchronous Method is actually executed in a specific thread object in the thread pool. The only thing not involved is how to be notified after the asynchronous function is executed. After all, blocking the call thread and waiting for the function to end is always unsatisfactory. To achieve this, we must provide a Callback delegate for the BeginInvoke function. Observe the two functions:
Private void CallSleepWithoutOutAndRefParameterWithCallback () {// create several parameters: string strParam = "Param1"; int intValue = 100; ArrayList list = new ArrayList (); list. add ("Item1"); // create the 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 indicates the out, so the initial value int extends utputvalue cannot be defined here; ArrayList list = null; // IAsyncResult is actually an AsyncResult object, // obtain the delegate object AsyncResult result = (AsyncResult) tag from which the function is called; // obtain the delegate DelegateWithParameters del = (DelegateWithParameters) result. asyncDelegate; // after obtaining the delegate, We need to execute EndInvoke on it. // You can obtain the execution result in the function. String strReturnValue = del. EndInvoke (out writable utputvalue, ref list, tag); Trace. WriteLine (strReturnValue );}
Here, we pass the Callback function to BeginInvoke. In this way,. NET can call the Callback function after FuncWithParameters () is executed. We have learned that EndInvoke must be used to obtain the execution result of the function. Note that in order to use EndInvoke, we have used some special operations to obtain the delegate object.
// IAsyncResult is actually an AsyncResult object. // obtain the AsyncResult result = (AsyncResult) tag of the delegate object used to call the function. // obtain the delegate DelegateWithParameters del = (parameters) result. asyncDelegate;
Last question: in what thread is the callback function executed?
All in all, the Callback function (Callback function) is called by. NET through our delegate object. We may want to get a clearer picture: In which thread does the callback function actually run? To achieve this goal: we add thread logs to the function.
Private string FuncWithParameters (out int param1, string param2, ref ArrayList param3) {// records Thread information Trace. WriteLine ("In FuncWithParameters: Thread Pool? "+ Thread. currentThread. isThreadPoolThread. toString () + "Thread Id:" + Thread. currentThread. getHashCode (); // wait a second to simulate a Thread running a long task here. sleep (4000); // here we change the parameter value param1 = 300; param2 = "hello"; param3 = new ArrayList (); // execute some time-consuming work threads here. sleep (3000); return "thank you for reading me";} private void CallBack (IAsyncResult tag) {// In what thread is the CallBack function executed? Trace. WriteLine ("In Callback: Thread Pool? "+ Thread. currentThread. isThreadPoolThread. toString () + "Thread Id:" + Thread. currentThread. getHashCode (); // our int parameter indicates out. Therefore, the initial value int unique utputvalue cannot be defined here; ArrayList list = null; // IAsyncResult is actually an AsyncResult object, // obtain the delegate object AsyncResult result = (AsyncResult) tag from which the function is called; // obtain the delegate DelegateWithParameters del = (DelegateWithParameters) result. asyncDelegate; // after obtaining the delegate, We need to execute EndInvoke on it. // You can obtain the execution result in the function. String strReturnValue = del. EndInvoke (out writable utputvalue, ref list, tag); Trace. WriteLine (strReturnValue );}
I put the CallSleepWithoutOutAndRefParameterWithCallback () function in the Click Event of a form button and click it three times in a row. The execution result is as follows:
Note that the FuncWithParameter function is executed three times in a row and is executed on independent threads in sequence. These threads come from the thread pool. Their respective callback functions are also executed in the same thread as FuncWithParameter. Thread 11 executes FuncWithParameter. After 3 seconds, its callback function is also executed in thread 11. The same is true for threads 12 and 13. In this way, we can think that the callback function is actually a continuation of the asynchronous function.
Why? This may be because we don't have to spend too much of the threads in the thread pool to achieve the effect of thread reuse. by executing the code in the same thread, it can also avoid the loss caused by passing context between different threads.
So far, when we execute an asynchronous function in Form, we will get an asynchronous call that does not block the main thread at all. This is what we hope!
Application Scenario Simulation
Now we understand the usage and features of BeginInvoke, EndInvoke, and Callback, and how to apply them to our Win Form program so that data acquisition does not block the UI thread, how does one load data asynchronously? A specific instance is used to describe it.
Scenario Description: Query the system operation logs from the database and load them to the front-end ListBox control.
Requirements: The Database Query Process is a job with a high time complexity. However, when executing a query on a form, we do not allow "false positives.
Private void button#click (object sender, EventArgs e) {GetLogDelegate getLogDel = new GetLogDelegate (GetLogs); getLogDel. beginInvoke (new AsyncCallback (LogTableCallBack), null);} public delegate DataTable GetLogDelegate (); // <summary> // obtain operation logs from the database, this operation takes a long time, // The returned data volume is large, and the number of log records may exceed. /// </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> /// bind logs 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 will not cause any blocking of the UI thread when obtaining data.
Summary:
The main purpose of this article is to summarize the methods for calling functions in non-blocking mode. We should understand the following conclusions;
● Delegate generates correct parameters for BeginInvoke and EndInvoke calls. All outgoing parameters, return values, and exceptions can be obtained in EndInvoke.
● Do not forget that BeginInvoke is taken from a thread in the thread pool. Be sure to prevent the number of asynchronous tasks from exceeding the thread upper limit of the thread pool.
● The CallBack delegate indicates the CallBack to an asynchronous task. It will free us from the congestion troubles.
● As of now, the UI thread will not be blocked when processing asynchronous work, but will only be blocked when updating the specific content of the UI.
Problem
We will find that once the data volume is large, our UI thread will still be "suspended" when loading the data to the control. This is normal, because we only ensure the independence between the retrieved data and the UI thread, and do not guarantee the thread busy problem caused by updating the UI, "False dead" is a user experience caused by busy UI threads. How to avoid this situation is further described below
Author Overflow