Asynchronous programming requires "awareness"

Although we live in an asynchronous world, asynchrony is still foreign to most beginners in programming. Learning a programming language usually starts with synchronous processes, i.e. sequences, branches and loops. And what is an async flow - start an async call and then... no then. Where did the asynchronous program go?

Asynchronous programs will run in some asynchronous form, such as multi-threading, asynchronous IO, etc., until the processing is completed. What if the result needs to be processed? Give a program entry, let it process the current process, send the processing result to this entry, and then execute another program - commonly known as callback. Callbacks generally use callbackthis name, but sometimes I prefer to use nextit because it represents the next processing step.

Synchronous and Asynchronous Concepts

Now we are exposed to some concepts like synchronization and asynchrony, what are they?

These two concepts are not derived from programming languages, but from low-level instructions, or even lower-level circuits. They are two concepts based on timing, where "step" refers to pacing, so synchronous means the same pacing, and asynchronous means different pacing. Of course, when these two concepts are raised to the level of the program, the precise meaning has nothing to do with the clock, but the meaning remains unchanged.

Synchronize

Take a real life example to illustrate the problem - rule out buying tickets. The ticket office opened a window, and a line of people lined up to buy tickets one after the other. In this team, if the person in front takes a step forward, the person behind can only take a step forward; the person in front is waiting, and the person in the back must be waiting. In an ideal world, then, everyone can move forward at the same time. OK, everyone is in the same pace, called synchronization.

Here, the ticket window is regarded as a processor, and everyone is regarded as an instruction waiting to be executed. The action of buying a ticket is to execute the instruction. Its feature is that it works step by step. If a person buys a ticket for too long (the instruction execution time is too long), it will cause blockage.

Asynchronous (multi-threaded)

Now more and more people are buying tickets, so the ticket office has opened several more windows to sell tickets at the same time. Each individual team is still in sync, but the pace is no longer consistent between the different teams, which is called asynchrony. The ticket sales in the A queue went smoothly, and the queue was advancing in an orderly and fast manner, but a customer in the B queue seemed to have some trouble paying, and it took a long time, causing congestion, but this did not affect the A queue.

At this time, the ticket office can be seen as running an asynchronous program in a multi-threaded manner. From this example, we can see two characteristics of asynchrony: first, the two asynchronous processes are independent of each other, and they will not block each other (there is a premise that there is no need to wait for shared resources); second, the internal asynchronous program Still synchronous .

Asynchronous (IO)

The above example is more in line with the multi-threaded asynchronous situation. What about IO asynchrony?

At the end of the year, M is preparing the materials for the year-end report. This is an intense job (CPU), and he has to collect a lot of data to write some copy. For one of the copies, M needs the production data of the workshop, but it takes a lot of time to make a trip to the workshop (IO), so he asks N to go to the workshop to collect data, and he continues to write other plans, while waiting for N to collect the data. come back (start the async program). Half a day later, N brought back the data (insert event message), M continued to complete the copy at hand (completed the current event loop), and then used the data brought back by N to start writing a report on the workshop (new event loop)...

The processing speed of IO is much slower than that of the CPU, so IO asynchrony allows the CPU to not have to sit idle waiting for the IO operation to complete. When the IO operation is completed, the CPU will continue to work with the IO operation result as appropriate.

Synchronous and asynchronous logic

Back to the program, we describe the synchronous and asynchronous processing with the processing of a function.

synchronization logic

Then, the synchronization process is:

接受输入 ⇒ 处理 ⇒ 产生输出

Described in a pseudo-code is

Note: The pseudocode in this article is closer to JavaScript syntax, and sometimes TypeScript's type declaration syntax is used to illustrate types.

function func(input) {
    do something with input
    return output
}

This is standard IPO (Input-Process-Output) processing.

Asynchronous logic

And asynchronous, is:

接受输入 ⇒ 处理 ⇒ 启动下一步(如果有)

Described in pseudocode is:

function asyncFunc(input, next) {
    do something with input
    if (next is a entry) {
        next(output)
    }
}

This process is called IPN (Input-Process-Next).

Note the Next, the next step, only one step. This step includes several subsequent steps. Therefore, this step can only encapsulate a module entry, or function, in the subsequent steps.

Therefore, the idea of ​​modularity is a very key idea in asynchronous thinking . Many beginners like to write code like a running account, and write down hundreds of lines of functions at every turn, which is a manifestation of the lack of modularity. The modular idea requires training, analyzing the correlation of codes, refining functions, and extracting objects. After having certain experience, it is necessary to master the granularity balance of module refinement. It doesn't happen overnight, but I recommend reading books on "design patterns" and "refactoring".

Asynchronous development tools (SDK and syntax level)

Promise

Think about the above example of the year-end report. When M asks N to go to the workshop to collect data, N will say: "Okay, I will bring the data back soon", which is a promise. Based on this commitment, M can arrange to write the report materials about the workshop later. This process is described in pseudocode as

function collectData(): Promise {
    // N 去收集数据,产生了一个承诺
    return new Promise(resolve => {
        collect data from workshop
        // 这个承诺最终会带来数据
        resolve(data)
    })
}

function writeWorkshopReport(data) {
    write report with data
}

// 收集数据的承诺兑现之后,可将这个数据用于写报告
collectData()
    .then(data => writeWorkshopReport(data))

Used in some language SDKs represented by JavaScript Promise. However, and are used in C Task# Task<T>, correspondingly, Task.ContinueWithand Task<T>.ContinueWithare used instead Promise.then.

Asynchronous logic synchronization

The difference between synchronous thinking and asynchronous thinking in one processing step was mentioned above. If you jump out of a processing step, from the perspective of a larger processing flow, there is not much difference between asynchronous and synchronous. Both 输入-->处理-->产生输出-->将输出用于下一步骤, the only thing to pay attention to is that you need to wait for the output generated by asynchronous processing. We can call it asynchronous waiting . Since we can do something else while doing async wait (await for short), this wait does not block. However, since this wait is declared, the compiler/interpreter will automatically place the code behind and call it after the wait completes, which makes asynchronous code write like synchronous code.

The pseudo code for the above example using async await would look like this

async function collectData(): Promise {
    collect data from workshop
    // 多数语言会把 async 函数的返回值封装成 Promise
    return data
}

function writeWorkshopReport(data) {
    write report with data
}

// await 只能用于声明为 async 的函数中
async function main() {
    data = await collectData()
    writeWorkshopReport(data)
}

// 定义了异步 main 函数,一定要记得调用,不然它是不会执行的
main()

Languages ​​such as C# and JavaScript stipulate that awaitmust be used in asyncfunctions declared as at the syntax level, which limits awaitthe . As long as it is used await, it must be in an asynchronous context. asyncIt also requires the compiler/interpreter to perform some automatic processing on its return value. For example, in JavaScript, if the return value is not a Promise object, it will be automatically encapsulated into a Promise object; in C#, it will be automatically encapsulated as Taskor Task<T>(So ​​the type of the asyncmethod needs to be declared as Taskor Task<T>).

Attention, attention, attention

Although Language Services has done a lot to synchronize asynchronous programs, there are still some human errors, such as forgetting to write awaitkeywords . In strongly typed languages ​​the compiler checks more strictly, but in JavaScript, forgetting to write a statement awaitthat means it should get a value will get a Promise. The interpreter will not challenge this, but the program will run incorrectly.

summary

In general, asynchronous programming is not particularly difficult. Using the async/await language feature it is even possible to write asynchronous code in a similar way to writing synchronous code. But syntactic sugar is sugar after all. To master asynchronous programming better, you still need to understand and be familiar with concepts such as asynchrony, callbacks, promises, modularity, design patterns, and refactoring.

Related Reading

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326434251&siteId=291194637