asyncio coroutine objects and Future objects use guide

  • 2020-05-10 18:23:17
  • OfStack

The relationship between coroutine and Future

It looks like both are one, because both can be obtained asynchronously using the following syntax,


result = await future
result = await coroutine

In fact, coroutine is a generator function that can either take arguments from the outside or produce results. The advantage of using coroutine is that we can pause a function and resume execution later. For example, in the case of network operations, the function can be stopped until the response arrives. During this time, we can switch to other tasks and continue.

Future is more like the Promise object in Javascript. It is a placeholder whose value will be calculated in the future. In the example above, while we are waiting for the network IO function to complete, the function will give us a container, and Promise will fill the container when it is finished. After filling, we can use the callback function to get the actual result.

The Task object is a subclass of Future, which connects coroutine and Future in 1, and encapsulates coroutine into an Future object.

You'll see two ways of starting a task in general,


tasks = asyncio.gather(
  asyncio.ensure_future(func1()),
  asyncio.ensure_future(func2())
)
loop.run_until_complete(tasks)

and


tasks = [
  asyncio.ensure_future(func1()),
  asyncio.ensure_future(func2())
  ]
loop.run_until_complete(asyncio.wait(tasks))

ensure_future can encapsulate coroutine into Task. asyncio.gather encapsulates 1 Future and 1 coroutine into 1 Future.

asyncio.wait itself is coroutine.

run_until_complete can receive either Future or coroutine objects,


BaseEventLoop.run_until_complete(future)

Run until the Future is done.
If the argument is a coroutine object, it is wrapped by ensure_future().
Return the Future's result, or raise its exception.

The correct way to exit an Task task

In the asyncio task cycle, if you exit using CTRL-C, even if you catch an exception, the task in Event Loop will report an error with the following error:

Task was destroyed but it is pending!
task: < Task pending coro= < kill_me() done, defined at test.py:5 > wait_for= < Future pending cb=[Task._wakeup()] > >

According to the official documentation, Task objects are considered to exit only in the following situations,

a result / exception are available, or that the future was cancelled

The cancel of the Task object is slightly different from its parent class, Future. When Task.cancel () is called, the corresponding coroutine throws an CancelledError exception in the next round of the event loop. Using Future.cancelled () does not immediately return True (used to indicate the end of the task); it is only cancelled after the above exception has been processed.

So the end of the task can be used


for task in asyncio.Task.all_tasks():
  task.cancel()

This method identifies and cancel all tasks.

However, CTRL-C will also stop the event cycle, so it is necessary to restart the event cycle.


try:
  loop.run_until_complete(tasks)
except KeyboardInterrupt as e:
  for task in asyncio.Task.all_tasks():
    task.cancel()
  loop.run_forever() # restart loop
finally:
  loop.close()

Catching exceptions in each Task is necessary and can be used if in doubt

asyncio.gather(..., return_exceptions=True)

The exception is converted to a normal result and returned.


Related articles: