简体   繁体   English

Python - 在异步中取消任务?

[英]Python - Cancel task in asyncio?

I have written code for async pool below.我在下面为异步池编写了代码。 in __aexit__ i'm cancelling the _worker tasks after the tasks get finished.__aexit__中,我将在任务完成后取消 _worker 任务。 But when i run the code, the worker tasks are not getting cancelled and the code is running forever.但是当我运行代码时,工作任务不会被取消并且代码会永远运行。 This what the task looks like: <Task pending coro=<AsyncPool._worker() running at \async_pool.py:17> wait_for=<Future cancelled>> .这是任务的样子: <Task pending coro=<AsyncPool._worker() running at \async_pool.py:17> wait_for=<Future cancelled>> The asyncio.wait_for is getting cancelled but not the worker tasks. asyncio.wait_for被取消,但没有工作任务。

class AsyncPool:
    def __init__(self,coroutine,no_of_workers,timeout):
        self._loop           = asyncio.get_event_loop()
        self._queue          = asyncio.Queue()
        self._no_of_workers  = no_of_workers
        self._coroutine      = coroutine
        self._timeout        = timeout
        self._workers        = None

    async def _worker(self): 
        while True:
            try:
                ret = False
                queue_item           = await self._queue.get()
                ret = True
                result               = await asyncio.wait_for(self._coroutine(queue_item), timeout = self._timeout,loop= self._loop)
            except Exception as e:
                print(e)
            finally:
                if ret:
                    self._queue.task_done()


    async def push_to_queue(self,item):
        self._queue.put_nowait(item)
    
    async def __aenter__(self):
        assert self._workers == None
        self._workers = [asyncio.create_task(self._worker()) for _ in range(self._no_of_workers)]
        return self
    
    async def __aexit__(self,type,value,traceback):
        await self._queue.join()

        for worker in self._workers:
            worker.cancel()

        await asyncio.gather(*self._workers, loop=self._loop, return_exceptions =True)

To use the Asyncpool:要使用异步池:

async def something(item):
    print("got", item)
    await asyncio.sleep(item)
 
async def main():
    async with AsyncPool(something, 5, 2) as pool:
        for i in range(10):
            await pool.push_to_queue(i)
 
asyncio.run(main())

The Output in my terminal:我终端中的输出: 在此处输入图像描述

When you have an asyncio task created and then cancelled, you still have the task alive that need to be "reclaimed".当您创建并取消了asyncio任务时,您仍然有需要“回收”的任务。 So you want to await worker for it.所以你想await worker However, once you await such a cancelled task, as it will never give you back the expected return value, the asyncio.CancelledError will be raised and you need to catch it somewhere.但是,一旦您await这样一个取消的任务,因为它永远不会给您返回预期的返回值, asyncio.CancelledError将被引发,您需要在某个地方捕获它。

Because of this behavior, I don't think you should gather them but to await for each of the cancelled tasks, as they are supposed to return right away:由于这种行为,我认为您不应该gather它们,而是await每个取消的任务,因为它们应该立即返回:

async def __aexit__(self,type,value,traceback):
    await self._queue.join()

    for worker in self._workers:
        worker.cancel()
    for worker in self._workers:
        try:
           await worker
        except asyncio.CancelledError:
           print("worker cancelled:", worker)

The problem is that your except Exception exception clause also catches cancellation, and ignores it.问题是您的except Exception异常子句也捕获取消,并忽略它。 To add to the confusion, print(e) just prints an empty line in case of a CancelledError , which is where the empty lines in the output come from.为了增加混乱, print(e)只是在CancelledError的情况下打印一个空行,这是输出中空行的来源。 (Changing it to print(type(e)) shows what's going on.) (将其更改为print(type(e))显示发生了什么。)

To correct the issue, change except Exception to something more specific, like except asyncio.TimeoutError .要更正此问题,请将except Exception更改为更具体的内容,例如except asyncio.TimeoutError This change is not needed in Python 3.8 where asyncio.CancelledError no longer derives from Exception , but from BaseException , so except Exception doesn't catch it.在 Python 3.8 中不需要此更改,其中asyncio.CancelledError不再派生自Exception ,而是派生自BaseException ,因此except Exception不会捕获它。

This appears to work.这似乎有效。 The event is a counting timer and when it expires it cancels the tasks.event是一个计数计时器,当它到期时,它会cancels任务。

import asyncio
from datetime import datetime as dt
from datetime import timedelta as td
import random
import time

class Program:
    def __init__(self):
        self.duration_in_seconds = 20
        self.program_start = dt.now()
        self.event_has_expired = False
        self.canceled_success = False

        

    async def on_start(self):
        print("On Start Event Start! Applying Overrides!!!")
        await asyncio.sleep(random.randint(3, 9))


    async def on_end(self):
        print("On End Releasing All Overrides!")
        await asyncio.sleep(random.randint(3, 9))
        

    async def get_sensor_readings(self):
        print("getting sensor readings!!!")
        await asyncio.sleep(random.randint(3, 9))   

 
    async def evauluate_data(self):
        print("checking data!!!")
        await asyncio.sleep(random.randint(3, 9))   


    async def check_time(self):
        if (dt.now() - self.program_start > td(seconds = self.duration_in_seconds)):
            self.event_has_expired = True
            print("Event is DONE!!!")

        else:
            print("Event is not done! ",dt.now() - self.program_start)



    async def main(self):
        # script starts, do only once self.on_start()
        await self.on_start()
        print("On Start Done!")

        while not self.canceled_success:

            readings = asyncio.ensure_future(self.get_sensor_readings())
            analysis = asyncio.ensure_future(self.evauluate_data())
            checker = asyncio.ensure_future(self.check_time())
            
            if not self.event_has_expired:
                await readings   
                await analysis           
                await checker
                
            else:
                # close other tasks before final shutdown
                readings.cancel()
                analysis.cancel()
                checker.cancel()
                self.canceled_success = True
                print("cancelled hit!")


        # script ends, do only once self.on_end() when even is done
        await self.on_end()
        print('Done Deal!')


async def main():
    program = Program()
    await program.main()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM