Current location - Quotes Website - Personality signature - Using Asyncio system in Python (3-4)? Mission and future
Using Asyncio system in Python (3-4)? Mission and future
Mission and future

Earlier, we discussed coroutines and how to run them in a loop. Now I want to talk briefly about the task and the future api. You will use Task most often, because most of your work will involve using the create_task () function to run the coroutine, as described in "Getting Started" on page 22. The Future class is actually a superclass of Task, which provides all the functions of interacting with loops.

It can be simply understood that the future represents the future completion state of an activity and is managed by the cycle. The tasks are exactly the same, but the specific "activity" is a process-it may be a process created by adding the async def function and create_task ().

The Future class represents the state of something that interacts with a loop. This description is too vague and useless, so you can imagine the future example as a switcher, a switcher in a finished state. When creating a future instance, the switch is set to the "incomplete" state, but later it will be the "completed" state. In fact, the Future instance has a method called done (), which allows you to check the status, as shown in Example 3- 15.

Example 3- 15. Check the completion status with the done () method.

Future instances can also do the following:

? Set a result value (with. Set_result(value). Results ()).

? Use. Cancel () method to cancel (and use. Cancelled () Check whether to cancel).

? Add a function that will be called back on future completion.

Even if Task is more common, it is impossible to completely avoid using the Future: for example, running a function on an executor will return a Future instance instead of Task. Let's take a quick look at example 3- 16 and see what it feels like to use the Future instance directly.

Example 3- 16. Interaction with future instances

(L3) Create a simple main function. Let's run this function, wait a moment, and then set the result of a future F.

(L5) Set a result.

(L8) Manually create future instances. Note that this instance (by default) is bound to our loop, but it is not and will not be attached to any coroutine (this is the role of the task).

(L9) Before doing anything, make sure that the future is not over.

(L 1 1) Organize the main () and inherit the future. Remember, all the main () process does is hibernate and then switch future instances. (Note that the main () coroutine will not start running yet: the coroutine will only start running during the event cycle. )

(L 13) Here we use run_until_complete () on the Future instance instead of the Task instance. This is different from what you saw before. Now that the loop is running, the main () coroutine will begin to execute.

(L 16) Finally, the future result is decided and it is finished. When finished, you can access the results.

Of course, you are unlikely to use the future; Directly in the way shown here; The code examples are for educational purposes only. Most of your contact with asynccio is through task instances.

You may wonder what happens if set_result () is called on a task instance. This was possible before Python 3.8, but it is not allowed now. Task instances are wrappers of coroutine objects, and their result values can only be set internally as the result of the underlying coroutine function, as shown in Example 3- 17.

Example 3- 17. Call set_result on the task.

(L 13) The only difference is that we created a task instance instead of a future instance. Of course, the task API requires us to provide a process; We use sleep () here only because it is simple and convenient.

(L7) Incoming task instance. It satisfies the type signature of the function (because Task is a subclass of Future), but since Python 3.8, we are no longer allowed to call set_result () on Task: trying to do so will lead to RuntimeError. The idea is that a task represents a running process, so the result should always come from the task itself.

(L 10, L24) However, we can still cancel () a task, which will result in a CancelledError in the underlying process.

Create _ Task? Ensure _ the future? Make up your mind!

In the "Quick Start" on page 22, I said that the way to run the process is to use asyncio.create_task (). Before introducing this function, it is necessary to get a loop instance and use loop.create_task () to accomplish the same task. In fact, this can also be achieved through a different module-level function: asyncio.ensure _ future (). Some developers recommend create_task (), while others recommend ensure _ future ().

During my research for this book, I am convinced that the API method asyncio.ensure _ future () is the chief culprit that leads to the widespread misunderstanding of the asyncio library. Most of the contents of API are clear, but there are still some serious obstacles in the learning process, and this is one of them. When you meet ensure _ future (), your brain will try very hard to integrate it into the mental model about how to use asyncio-but it is likely to fail!

In the Python 3.6 asyncio document, this now infamous explanation highlights the problem of guarantee _ future ():

asyncio . assure _ future(coro _ or _ future,*,_loop =None)

Arrange to execute a process object: package it in the future. Returns a task object. If the parameter is Future, it is returned directly.

What! ? When I first read this article, I was very confused. The following hope is a clearer description of ensure _ future ():

This function illustrates the difference between asyncio API (advanced API) for end-user developers and asyncio API (low API) for framework designers. Let's teach ourselves in Example 3- 18 and see how it works.

Example 3- 18. Take a closer look at what ensure _ future () is doing.

(L3) A simple process function that does nothing. We just need something that can make up a concert.

(L6) We create a coroutine object by calling this function directly. Your code rarely does this, but I want to make it clear here that we are passing a coroutine object to each create_task () and ensure _ future ().

(L7) Get a cycle.

(L9) First, we use loop.create_Task () to schedule the coroutine in the loop and return a new task instance.

(L 10) authentication type. So far, nothing interesting.

(L 12) We showed that asyncio.ensure _ future () can be used to perform the same operation as create_Task (): we passed in a coroutine and returned a task instance (and the coroutine has been scheduled to run in a loop)! If it is a process, there is no difference between loop.create_task () and asyncio.ensure _ future ().

(L 15) What happens if we pass a task instance to ensure _ future ()? Note that the task instance we want to pass has been created by loop.create_task () in step 4.

(L 16) The returned task instance is exactly the same as the incoming task instance: it was not changed when it was passed.

What's the point of directly using future examples? Why use the same function to do two different things? The answer is that the purpose of ensuring the future () is to let the framework author provide an API that can handle two kinds of parameters to the end-user developer. Don't believe me? This is what the former BDFL himself said:

The point of ensure _ Future () is that if you have something that may be a coroutine or the future (the latter contains a task because it is a subclass of the future), and you want to be able to call a method defined only in the future (perhaps the only useful example is cancel ()). When it is already the future (or task), it does nothing; When it is a process, it wraps it in a task.

If you know that you have a coroutine and want to schedule it, the correct API is create_task (). Ensure _ Future () should be called only when an API (like most asyncio's own API) is provided, which accepts coroutine or Future. You need to do something about it. You need to have a future.

-Guido van Rossum

In a word, asyncio.sure_future () is an auxiliary function of the framework designer. This is the easiest to explain by a more common function analogy, so let's do this explanation. If you have several years of programming experience, you may have seen a function similar to the istify () function in Example 3- 19. The function of listify () in example 3- 19.

Example 3- 19. Tool function of forced input list

This function attempts to convert parameters into a list, regardless of what is entered. This function is often used in APIs and frameworks to cast input to a known type, which will simplify the subsequent code-in this case, you know that the parameter (the output of listify ()) will always be a list.

If I rename the listify () function to ensure _ list (), then you should start to see its similarity with asyncio.ensure _ Future (): it always tries to cast parameters to Future (or subclasses) types. This is a utility function that makes the work of framework developers (not end-user developers like you and me) easier.

In fact, the asyncio standard library module itself uses ensure _ future () for this reason. The next time you look at the API, you will find that the function parameters are described as "waitable objects", probably because the guarantee _ future () is used internally to cast the parameters. For example, the asyncio.gather () function is similar to the following code:

Aws parameters represent "objects that can wait", including contract, task and future. Internally, gather () uses ensure _ future () for type coercion: task and future remain unchanged, while coroutine is forced as task.

The key point here is that as an end-user application developer, you should never need to use asyncio.ensure _ future (). It's more like a tool for frame designers. If you need to schedule a coroutine on the event loop, just use asyncio.create_task () directly.

In the next few sections, we will return to language-level features, starting with the asynchronous context manager.