SoFunction
Updated on 2025-03-01

Async call instance tutorial for basic C#

This article shows the implementation method of asynchronous calls in C# in the example form, and conducts a more in-depth analysis of its principles. We will share them with you in the form of a tutorial for your reference. The details are as follows:

First, let’s take a look at a simple example:

Xiao Ming boils water, and after the water boils, he pours the boiling water into the thermos bottle, and then starts to organize the housework.
Xiaowen is boiling water and sorting out housework during the boiling process. After the water boils, he put down the housework in his hand, pour the boiling water into the thermos bottle, and then continue to sort out housework.
This is also a very common situation in daily life. Xiaowen's efficiency is obviously higher than that of Xiaoming. From the perspective of C# program execution, Xiao Ming uses synchronous processing methods, while Xiao Wen uses asynchronous processing methods.

Under the synchronous processing method, transactions are processed one by one in sequence; while the asynchronous method is to separate the sub-operation from the main operation, the main operation continues, and the sub-operation notifies the main operation when the processing is completed.

In C#, asynchronously is done through delegates. Please see the following example:

class Program  
{  
  static TimeSpan Boil()  
  {  
    ("Keep: Start boiling water...");  
    (6000);  
    ("Keep: The water has boiled!");  
    return ;  
  }  
 
  delegate TimeSpan BoilingDelegate();  
 
  static void Main(string[] args)  
  {  
    ("Xiaowen: Put the kettle on the stove");  
    BoilingDelegate d = new BoilingDelegate(Boil);  
    IAsyncResult result = (BoilingFinishedCallback, null);  
    ("Xiaowen: Start tidying up housework...");  
    for (int i = 0; i < 20; i++)  
    {  
      ("Little article:Organize the first{0}Housework...", i + 1);  
      (1000);  
    }  
  }  
 
  static void BoilingFinishedCallback(IAsyncResult result)  
  {  
    AsyncResult asyncResult = (AsyncResult)result;  
    BoilingDelegate del = (BoilingDelegate);
    (result);
    ("Xiaowen: Fill hot water into a thermos bottle");  
    ("Xiaowen: Continue to organize housework");   
  }  
}

The above example is the simplest example of an asynchronous call, without any parameter passing and return value verification on the asynchronous call function. This example reflects Xiaowen's process of boiling water. First, Xiaowen puts the kettle on the stove. After defining the commission, he uses the BeginInvoke method to start calling asynchronously, that is, let the kettle start boiling water, so Xiaowen starts to organize housework. After the water boils, the asynchronous model of C# will trigger the callback function specified by the BeginInvoke method, that is, the processing logic after the water boils is defined by this callback function. At this time, Xiaowen pours water into the thermos bottle and continues to organize housework.

It can be seen that implementing asynchronous calls in C# is not complicated. First, create an asynchronous processing function and define a delegate for it. Then, when calling the function, use the delegate BeginInvoke method to specify the callback function when the function processing is completed (if there is no need to process the completion event, you can give a null value), and specify the required parameters (if there is no parameter, you can also give a null value); finally handle the completion event in the callback function.

Please note that the EndInvoke call in the callback function in the above example is called. EndInvoke will cause the calling thread to block until the asynchronous function processing is completed. Obviously, the usage of EndInvoke immediately after BeginInvoke is equivalent to synchronous calls.

The return value of the EndInvoke call is the return value of the asynchronous processing function. We slightly modify the program and change the Boil method to the following form:

static TimeSpan Boil()  
{  
  DateTime begin = ;  
  ("Keep: Start boiling water...");  
  (6000);  
  ("Keep: The water has boiled!");  
  return  - begin;  
}  

Then change BoilingFinishedCallback to the following form:

static void BoilingFinishedCallback(IAsyncResult result)  
{  
  AsyncResult asyncResult = (AsyncResult)result;  
  BoilingDelegate del = (BoilingDelegate);  
  ("(Use boiling water to total{0}time)", (result));  
  ("Xiaowen: Fill hot water into a thermos bottle");  
  ("Xiaowen: Continue to organize housework");  
}  

Then we can obtain the time value returned by the Boil asynchronous processing function when EndInvoke. In fact, if the defined BoilingDelegate delegate has a parameter list, then we can also pass the required parameters to the asynchronous processing function when BeginInvoke. The signature of the BeginInvoke/EndInvoke function is related to the delegate signature that defines them.

Note: In the modified BoilingFinishedCallback method, in order to obtain the delegate instance to obtain the return value of the asynchronous processing function, we adopted the following transformation:

AsyncResult asyncResult = (AsyncResult)result;  
BoilingDelegate del = (BoilingDelegate);  

This way, the entity that calls the delegate of the asynchronous processing function can be obtained.

.NET handles asynchronous function calls, which are actually done through threads. This process has the following characteristics:

1. The asynchronous function is completed by a thread, which is a thread in the .NET thread pool.

2. Generally speaking, the .NET thread pool has 500 threads (of course this number can be set). Whenever BeginInvoke is called to start asynchronous processing, the asynchronous processing function is executed by a thread in the thread pool, and the user cannot control which thread is responsible for the execution.

3. Since the number of threads in the thread pool is limited, when the threads in the pool are completely occupied, the new call request will make the function have to wait for the appearance of the spare thread. At this time, the efficiency of the program will have some impact.

To verify these characteristics, please see the following procedure:

class Program  
{  
  delegate void MethodInvoker();  
  static void Foo()  
  {  
    int intAvailableThreads, intAvailableIoAsynThreds;  
    (out intAvailableThreads, out intAvailableIoAsynThreds);  
    string strMessage = (@"Is Thread Pool: {0},  
    Thread Id: {1} Free Threads {2}",  
        (),  
        (),  
        intAvailableThreads);  
    (strMessage);  
    (10000);  
    return;  
  }  
 
  static void CallFoo()  
  {  
    MethodInvoker simpleDelegate = new MethodInvoker(Foo);  
    for (int i = 0; i < 15; i++)  
    {  
      (null, null);  
    }  
  }  
  static void Main(string[] args)  
  {  
    (10, 10);  
    CallFoo();  
    ();  
  }  
}

At the beginning, this program sets the maximum number of threads in the thread pool to 10, and then makes 15 asynchronous calls, each asynchronous call stays for 10 seconds as the time it takes to process itself. From the execution of the program, we can see that after the current 10 asynchronous calls have completely started, the new asynchronous calls will wait (note: it is not the main thread waiting) until there are threads in the thread pool free of them.

I hope this article will be helpful to everyone's C# programming.