跳转至

14. 防止任务被取消

14. Shield Tasks from Cancellation

可以通过调用异步任务的 cancel() 方法来取消异步任务。

我们可以通过将任务包装在对 asyncio.shield() 的调用中来防止任务被取消。

让我们仔细看看。

Asyncio tasks can be canceled by calling their cancel() method.

We can protect a task from being canceled by wrapping it in a call to asyncio.shield().

Let’s take a closer look.

14.1 什么是 Asyncio shield()

14.1 What is Asyncio shield()

asyncio.shield() 函数在 Future 中包装了一个可等待的对象,它将吸收要取消的请求。

保护可等待对象不被取消。

COROUTINES AND TASKS

这意味着受屏蔽的 future 可以传递给可能尝试取消它的任务,并且取消请求看起来像是成功的,只不过被屏蔽的任务或协程将继续运行。

它在异步程序中可能很有用,其中某些任务可以取消,但其他任务(可能具有更高优先级)则不能。

它在某些任务可以安全取消的程序中也可能很有用,例如那些设计时考虑了 asyncio 的任务,而其他任务则无法安全终止,因此必须防止取消。

现在我们知道了 asyncio.shield() 是什么,让我们看看如何使用它。

The asyncio.shield() function wraps an awaitable in Future that will absorb requests to be canceled.

Protect an awaitable object from being cancelled.

COROUTINES AND TASKS

This means the shielded future can be passed around to tasks that may try to cancel it and the cancellation request will look like it was successful, except that the Task or coroutine that is being shielded will continue to run.

It may be useful in asyncio programs where some tasks can be canceled, but others, perhaps with a higher priority cannot.

It may also be useful in programs where some tasks can safely be canceled, such as those that were designed with asyncio in mind, whereas others cannot be safely terminated and therefore must be shielded from cancellation.

Now that we know what asyncio.shield() is, let’s look at how to use it.

14.2 如何使用 Asyncioshield()

14.2 How to Use Asyncio shield()

asyncio.shield() 函数将保护另一个 Task 或协程不被 取消。

它接受一个可等待作为参数并返回一个 asyncio.Future 对象。

然后可以直接等待 Future 对象或将其传递给另一个任务或协程。

例如:

...
# 防止任务被取消
shielded = asyncio.shield(task)
# 等待屏蔽任务
await shielded

返回的 Future 可以通过调用 cancel() 方法取消。

如果内部任务正在运行,则请求将报告为成功。

例如:

...
# 取消屏蔽任务
was_canceld = shielded.cancel()

任何等待 Future 对象的协程都会引发 asyncio.CancelledError,这可能需要处理。

例如:

...
try:
    # 等待屏蔽任务
    await asyncio.shield(task)
except asyncio.CancelledError:
    # ...

重要的是,对 Future 对象发出的取消请求不会传播到内部任务。

这意味着取消请求被屏蔽吸收。

例如:

...
# 创建任务
task = asyncio.create_task(coro())
# 创建一个防取消
shield = asyncio.shield(task)
# 取消屏蔽(不取消任务)
shield.cancel()

如果向 asyncio.shield() 函数提供协程,它将被包装在 asyncio.Task() 中并立即调度。

这意味着屏蔽不需要等待内部协程运行。

如果 aw 是协程,它会自动安排为任务。

COROUTINES AND TASKS

如果正在屏蔽的任务被取消,则取消请求将传播到屏蔽,屏蔽也将被取消。

例如:

...
# 创建任务
task = asyncio.create_task(coro())
# 创建一个盾牌
shield = asyncio.shield(task)
# 取消任务(同时取消护盾)
task.cancel()

现在我们知道如何使用 asyncio.shield() 函数,让我们看一些有效的示例。

The asyncio.shield() function will protect another Task or coroutine from being canceled.

It takes an awaitable as an argument and returns an asyncio.Future object.

The Future object can then be awaited directly or passed to another task or coroutine.

For example:

...
# shield a task from cancellation
shielded = asyncio.shield(task)
# await the shielded task
await shielded

The returned Future can be canceled by calling the cancel() method.

If the inner task is running, the request will be reported as successful.

For example:

...
# cancel a shielded task
was_canceld = shielded.cancel()

Any coroutines awaiting the Future object will raise an asyncio.CancelledError, which may need to be handled.

For example:

...
try:
    # await the shielded task
    await asyncio.shield(task)
except asyncio.CancelledError:
    # ...

Importantly, the request for cancellation made on the Future object is not propagated to the inner task.

This means that the request for cancellation is absorbed by the shield.

For example:

...
# create a task
task = asyncio.create_task(coro())
# create a shield
shield = asyncio.shield(task)
# cancel the shield (does not cancel the task)
shield.cancel()

If a coroutine is provided to the asyncio.shield() function it is wrapped in an asyncio.Task() and scheduled immediately.

This means that the shield does not need to be awaited for the inner coroutine to run.

If aw is a coroutine it is automatically scheduled as a Task.

— COROUTINES AND TASKS

If the task that is being shielded is canceled, the cancellation request will be propagated up to the shield, which will also be canceled.

For example:

...
# create a task
task = asyncio.create_task(coro())
# create a shield
shield = asyncio.shield(task)
# cancel the task (also cancels the shield)
task.cancel()

Now that we know how to use the asyncio.shield() function, let’s look at some worked examples.

14.3 任务的 Asyncioshield() 示例

14.3 Example of Asyncio shield() for a Task

我们可以探索如何使用 asyncio.shield() 来保护任务不被取消。

在这个例子中,我们定义了一个简单的协程任务,它接受一个整数参数,休眠一秒钟,然后返回该参数。 然后可以创建协程并将其安排为任务。

我们可以定义第二个协程,它接受一个任务,休眠一小会儿,然后取消提供的任务。

在主协程中,我们可以屏蔽第一个任务并将其传递给第二个任务,然后等待屏蔽的任务。

预计护盾将被取消,而内部任务完好无损。 取消将扰乱主协程。 我们可以在程序结束时检查内部任务的状态,并且我们希望它已正常完成,无论屏蔽上是否发出取消请求。

下面列出了完整的示例。

# SuperFastPython.com
# 使用 asyncio shield 保护任务不被取消的示例
import asyncio

# 定义一个简单的异步
async def simple_task(number):
    # 暂时阻塞
    await asyncio.sleep(1)
    # 返回参数
    return number

# cancel the given task after a moment
async def cancel_task(task):
    # 暂时阻塞
    await asyncio.sleep(0.2)
    # 取消任务
    was_cancelled = task.cancel()
    print(f'cancelled: {was_cancelled}')

# 定义一个简单的协程
async def main():
    # 创建协程
    coro = simple_task(1)
    # 创建任务
    task = asyncio.create_task(coro)
    # 创建被保护的任务
    shielded = asyncio.shield(task)
    # 创建任务以取消上一个任务
    asyncio.create_task(cancel_task(shielded))
    # 处理取消
    try:
        # 等待屏蔽任务
        result = await shielded
        # 报告结果
        print(f'>got: {result}')
    except asyncio.CancelledError:
        print('shielded was cancelled')
    # 稍等
    await asyncio.sleep(1)
    # 报告任务的详细信息
    print(f'shielded: {shielded}')
    print(f'task: {task}')

# 开始
asyncio.run(main())

运行该示例首先创建 main() 协程并将其用作应用程序的入口点。

创建任务协程,然后将其包装并安排在 Task 中。

然后该任务就不会被取消。

然后,屏蔽任务被传递到 cancel_task() 协程,该协程被包装在任务中并进行调度。

然后,主协程等待屏蔽任务,该任务需要 CancelledError 异常。

该任务运行一会儿然后休眠。 取消任务运行一会儿,休眠,恢复,然后取消屏蔽任务。 取消请求报告称已成功。

这会在屏蔽的 Future 中引发 CancelledError 异常,但不会在内部任务中引发。

main() 协程恢复并响应 CancelledError 异常,报告一条消息。 然后它会再睡一会儿。

任务继续、完成并返回一个值。

最后,main() 协程恢复,并报告屏蔽 future 和内部任务的状态。 我们可以看到,屏蔽的 future 被标记为已取消,而内部任务被标记为正常完成并提供返回值。

此示例重点介绍了如何使用防护罩成功保护内部任务免遭取消。

cancelled: True
shielded was cancelled
shielded: <Future cancelled>
task: <Task finished name='Task-2' coro=<simple_task() done, defined at ...> result=1>

您可以在教程中了解有关shield()函数的更多信息:

接下来,我们将探讨如何从 asyncio 程序运行阻塞任务。

We can explore how to protect a task from cancellation using asyncio.shield().

In this example, we define a simple coroutine task that takes an integer argument, sleeps for a second, then returns the argument. The coroutine can then be created and scheduled as a Task.

We can define a second coroutine that takes a task, sleeps for a fraction of a second, then cancels the provided task.

In the main coroutine, we can then shield the first task and pass it to the second task, then await the shielded task.

The expectation is that the shield will be canceled and leave the inner task intact. The cancellation will disrupt the main coroutine. We can check the status of the inner task at the end of the program and we expect it to have been completed normally, regardless of the request to cancel made on the shield.

The complete example is listed below.

# SuperFastPython.com
# example of using asyncio shield to protect a task from cancellation
import asyncio

# define a simple asynchronous
async def simple_task(number):
    # block for a moment
    await asyncio.sleep(1)
    # return the argument
    return number

# cancel the given task after a moment
async def cancel_task(task):
    # block for a moment
    await asyncio.sleep(0.2)
    # cancel the task
    was_cancelled = task.cancel()
    print(f'cancelled: {was_cancelled}')

# define a simple coroutine
async def main():
    # create the coroutine
    coro = simple_task(1)
    # create a task
    task = asyncio.create_task(coro)
    # created the shielded task
    shielded = asyncio.shield(task)
    # create the task to cancel the previous task
    asyncio.create_task(cancel_task(shielded))
    # handle cancellation
    try:
        # await the shielded task
        result = await shielded
        # report the result
        print(f'>got: {result}')
    except asyncio.CancelledError:
        print('shielded was cancelled')
    # wait a moment
    await asyncio.sleep(1)
    # report the details of the tasks
    print(f'shielded: {shielded}')
    print(f'task: {task}')

# start
asyncio.run(main())

Running the example first creates the main() coroutine and uses it as the entry point into the application.

The task coroutine is created, then it is wrapped and scheduled in a Task.

The task is then shielded from cancellation.

The shielded task is then passed to the cancel_task() coroutine which is wrapped in a task and scheduled.

The main coroutine then awaits the shielded task, which expects a CancelledError exception.

The task runs for a moment then sleeps. The cancellation task runs for a moment, sleeps, resumes then cancels the shielded task. The request to cancel reports that it was successful.

This raises a CancelledError exception in the shielded Future, although not in the inner task.

The main() coroutine resumes and responds to the CancelledError exception, reporting a message. It then sleeps for a while longer.

The task resumes, finishes, and returns a value.

Finally, the main() coroutine resumes, and reports the status of the shielded future and the inner task. We can see that the shielded future is marked as canceled and yet the inner task is marked as finished normally and provides a return value.

This example highlights how a shield can be used to successfully protect an inner task from cancellation.

cancelled: True
shielded was cancelled
shielded: <Future cancelled>
task: <Task finished name='Task-2' coro=<simple_task() done, defined at ...> result=1>

You can learn more about the shield() function in the tutorial:

Next, we will explore how to run a blocking task from an asyncio program.


最后更新: 2024年9月4日
创建日期: 2024年9月4日