1. What is asynchronous?
In general, two workflows can be performed simultaneously and are considered asynchronous. For example, the workflow between the CPU and the peripheral is asynchronous. In service-oriented systems, communication between each subsystem is generally asynchronous. For example, the communication between the order system and the payment system is asynchronous. For example, in real life, when you go to a restaurant for a meal, the workflow is like this: Ordering food->ordering orders->doing your thing->serving food->eating, this is also asynchronous. Specifically, between you and the chef is asynchronous. Asynchronous is so important because it represents high efficiency (the work of both or more can be carried out at the same time), but it is complex, and the synchronous world is simple, but extremely low efficiency.
2. Asynchronous in programming
In programming, in addition to the two nouns of synchronization and asynchronous, there is also an additional blocking and non-blocking. Among them, blocking and non-blocking are the concepts of threads, so who are synchronization and asynchronous? In fact, in many cases, synchronization and asynchronous do not specifically target a certain thing, which leads to the fuzzy concepts of synchronous blocking, synchronous non-blocking, asynchronous blocking, and asynchronous non-blocking. And there is indeed no clear boundary, please see the following example:
public static void DoWorkA() { Thread thread = new Thread(() => { ("WorkA Done!"); }); (); } public static void DoWordB() Thread thread = new Thread(() => ("WorkB Done!"); static void Main(string[] args) DoWorkA(); DoWordB();
Assuming that the CPU running this code is a single core and a single thread, then? Are the two functions DoWorkA() and DoWorkB() asynchronous? Because the CPU is a single core, two functions cannot be run at the same time. From this level, they are actually synchronous. However, the reality is that we generally believe that they are asynchronous because we consider them from the perspective of the execution order of the code, not from the workflow of the CPU itself. So it needs to be considered in context. Please see the following example:
static void Main(string[] args) { DoWorkA(); QueryDataBaseSync();//Synchronous query database DoWorkB(); }
From the perspective of code execution order, the execution of these three functions is synchronous. However, from the perspective of the CPU, the database query work (another machine) and the CPU computing work are asynchronous. In the following, no special statement is made, synchronization and asynchronous are discussed from the perspective of code execution order.
Let’s explain the blocking and non-blocking and related knowledge:
Blocking specifically refers to the thread transitioning from the running state to the suspended state, but the CPU does not block. The operating system will switch another thread in the ready state and convert it to the running state. There are many reasons for thread blocking, such as: system calls occur (the application calls the system API. If the call is successful, the conversion overhead from application->kernel->application->application->application->application->application->user conditions are not met at this time. For example, reading data from the Socket kernel buffer, and the buffer has no data yet, it will cause the operating system to suspend the thread, switch to another thread in the ready state and then execute it to the CPU. This is caused by active calls, and it is also caused by passiveness. For the current time-sharing operating system, a clock interrupt signal will occur after a thread time slice has arrived, and then it will be processed by the interrupt function pre-written by the operating system, and then switch to another thread for execution according to a certain strategy (such as thread priority), causing the thread to passively convert from running state to suspend state.
Non-blocking generally means that a function call does not cause the thread executing the function to be converted from a running state to a suspended state.
3. The original callback function of the asynchronous programming mode
Before this, let’s first understand the working principle of the graphical interface. GUI programs can be roughly represented by the following pseudo-code:
While(GetMessage() != 'exit') //Get a message from the thread message queue. The thread message queue is maintained by the system, such as a mouse movement event, which is captured by the operating system and delivered to the thread's message queue.{ msg = TranslateMessage();//Convert message format DispatherMessage(msg);//Distribute the message to the corresponding processing function}
DispatherMessage calls different message processing functions according to different message types, such as MouseMove. At this time, the message processing function can perform corresponding processing based on the value in the MouseMove message, such as calling drawing-related functions to draw the shape of the mouse at this moment.
Generally speaking, we call this loop a message loop (event loop, EventLoop), and the programming model is called a message-driven model (event-driven). In a UI program, there is generally only one thread that executes this part of the code, which is called a UI thread. Why is it a single thread? Readers can think about it.
The above is background knowledge. Now, let's think about what would happen if an operation in a UI thread would cause the UI thread to be blocked, or a pure CPU calculation work was performed on a UI thread? If an operation that causes the UI thread to be blocked, the message loop will be forced to stop, resulting in the relevant drawing messages that cannot be processed by the corresponding message processing function, which is manifested as the UI interface "fake death" until the UI thread is called. If it is pure CPU computing, it will also cause other messages to not be processed in time, which will also lead to the phenomenon of "fake death" on the interface. How to deal with this situation? Write asynchronous code.
We first use the console program to simulate this UI program, and then use it as the basis.
public static string GetMessage() { return (); } public static string TranslateMessage(string msg) return msg; public static void DispatherMessage(string msg) switch (msg) { case "MOUSE_MOVE": { OnMOUSE_MOVE(msg); break; } default: break; } public static void OnMOUSE_MOVE(string msg) ("Start drawing mouse shape"); static void Main(string[] args) while(true) string msg = GetMessage(); if (msg == "quit") return; string m = TranslateMessage(msg); DispatherMessage(m);
1. Callback function
In the example above, once a message arrives outside, different processing functions are called according to different message types. For example, when the mouse moves, the corresponding message processing function begins to re-draw the mouse shape. In this way, once the mouse moves, you will find that the mouse on the screen moves accordingly.
Now suppose we add a message processing function, such as OnMOUSE_DOWN, a blocking operation is performed inside this function. For example, if an HTTP request is initiated, the UI program will "fake it dead" before the HTTP request reply comes. We write asynchronous code to solve this problem.
public static int Http() { (1000);//Simulate network IO delay return 1; } public static void HttpAsync(Action<int> action,Action error) { // Here we use another thread to implement asynchronous IO. Since the Http method simulates network IO delay through Sleep, we can only implement asynchronous IO through another thread here. //But remember that multithreading is just a means to implement asynchronous IO, it is not necessary. Later, we will talk about how to implement asynchronous IO through a thread. Thread thread = new Thread(() => { try { int res = Http(); action(res); } catch { error(); } }); (); } public static void OnMouse_DOWN(string msg) { HttpAsync(res => { ("The request succeeded!"); // Use this result to do some work }, () => { ("The request error occurred!"); }); }
At this time, the interface is no longer "fake death". Let's look at the readability of the code and feel it's OK, but what if you initiate a similar asynchronous request in the callback function? (Someone may have questions, why do you still need to initiate an asynchronous request? Can't I send a synchronous request? This is all in another thread. Yes, it is fine in this example, but the real situation is that the code for executing the callback function is generally in the UI thread, because after obtaining the result, it is necessary to update the interface on the relevant UI components, such as text, and the operation of the update interface is placed in the UI thread. How to put the callback function on the UI thread to execute, I will not discuss it here. In .NET, this is related to the synchronization context (Synchronization context), which will be discussed later), so the code will become like this.
public static void OnMouse_DOWN(string msg) { HttpAsync(res => { ("The request succeeded!"); // Use this result to do some work HttpAsync(r1 => { // Use this result to do some work HttpAsync(r2 => { // Use this result to do some work }, () => { }); }, () => { }); }, () => { ("The request error occurred!"); }); }
Students who have written JS may be very clear that this is called "callback hell". How to solve this problem? There is Promise in JS, and there is Task in C#. We first use Task to write this code, and then implement a simple class library that is similar to the Task function.
public static Task<int> HttpAsync() { return (() => { return Http(); }); } public static void OnMouse_DOWN(string msg) HttpAsync() .ContinueWith(t => { if( == ) { }else if( == ) //Do some work } }) if ( == ) else if ( == ) });
Does it feel much more refreshing? This is the first leap forward in writing asynchronous code. The next article will introduce how to implement a simple task by yourself. We will also mention the essential role of async/await in C#, how async/await is associated with Task, how to connect the Task library you wrote with async/await, and how a thread implements asynchronous IO.
This is the article about C# asynchronous programming from shallow to deep (1). For more related C# asynchronous programming content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!