python取消任务的方法_python asyncio,如何从另一个线程创建和取消任务

I have a python multi-threaded application. I want to run an asyncio loop in a thread and post calbacks and coroutines to it from another thread. Should be easy but I cannot get my head around the asyncio stuff.

I came up to the following solution which does half of what I want, feel free to comment on anything:

import asyncio

from threading import Thread

class B(Thread):

def __init__(self):

Thread.__init__(self)

self.loop = None

def run(self):

self.loop = asyncio.new_event_loop()

asyncio.set_event_loop(self.loop) #why do I need that??

self.loop.run_forever()

def stop(self):

self.loop.call_soon_threadsafe(self.loop.stop)

def add_task(self, coro):

"""this method should return a task object, that I

can cancel, not a handle"""

f = functools.partial(self.loop.create_task, coro)

return self.loop.call_soon_threadsafe(f)

def cancel_task(self, xx):

#no idea

@asyncio.coroutine

def test():

while True:

print("running")

yield from asyncio.sleep(1)

b.start()

time.sleep(1) #need to wait for loop to start

t = b.add_task(test())

time.sleep(10)

#here the program runs fine but how can I cancel the task?

b.stop()

So starting and stoping the loop works fine. I thought about creating task using create_task, but that method is not threadsafe so I wrapped it in call_soon_threadsafe. But I would like to be able to get the task object in order to be able to cancel the task. I could do a complicated stuff using Future and Condition, but there must be a simplier way, isnt'it?

解决方案

I think you may need to make your add_task method aware of whether or not its being called from a thread other than the event loop's. That way, if it's being called from the same thread, you can just call asyncio.async directly, otherwise, it can do some extra work to pass the task from the loop's thread to the calling thread. Here's an example:

import time

import asyncio

import functools

from threading import Thread, current_thread, Event

from concurrent.futures import Future

class B(Thread):

def __init__(self, start_event):

Thread.__init__(self)

self.loop = None

self.tid = None

self.event = start_event

def run(self):

self.loop = asyncio.new_event_loop()

asyncio.set_event_loop(self.loop)

self.tid = current_thread()

self.loop.call_soon(self.event.set)

self.loop.run_forever()

def stop(self):

self.loop.call_soon_threadsafe(self.loop.stop)

def add_task(self, coro):

"""this method should return a task object, that I

can cancel, not a handle"""

def _async_add(func, fut):

try:

ret = func()

fut.set_result(ret)

except Exception as e:

fut.set_exception(e)

f = functools.partial(asyncio.async, coro, loop=self.loop)

if current_thread() == self.tid:

return f() # We can call directly if we're not going between threads.

else:

# We're in a non-event loop thread so we use a Future

# to get the task from the event loop thread once

# it's ready.

fut = Future()

self.loop.call_soon_threadsafe(_async_add, f, fut)

return fut.result()

def cancel_task(self, task):

self.loop.call_soon_threadsafe(task.cancel)

@asyncio.coroutine

def test():

while True:

print("running")

yield from asyncio.sleep(1)

event = Event()

b = B(event)

b.start()

event.wait() # Let the loop's thread signal us, rather than sleeping

t = b.add_task(test()) # This is a real task

time.sleep(10)

b.stop()

First, we save the thread id of the event loop in the run method, so we can figure out if calls to add_task are coming from other threads later. If add_task is called from a non-event loop thread, we use call_soon_threadsafe to call a function that will both schedule the coroutine, and then use a concurrent.futures.Future to pass the task back to the calling thread, which waits on the result of the Future.

A note on cancelling a task: You when you call cancel on a Task, a CancelledError will be raised in the coroutine the next time the event loop runs. This means that the coroutine that the Task is wrapping will aborted due to the exception the next time it hit a yield point - unless the coroutine catches the CancelledError and prevents itself from aborting. Also note that this only works if the function being wrapped is actually an interruptible coroutine; an asyncio.Future returned by BaseEventLoop.run_in_executor, for example, can't really be cancelled, because it's actually wrapped around a concurrent.futures.Future, and those can't be cancelled once their underlying function actually starts executing. In those cases, the asyncio.Future will say its cancelled, but the function actually running in the executor will continue to run.

Edit: Updated the first example to use concurrent.futures.Future, instead of a queue.Queue, per Andrew Svetlov's suggestion.

Note: asyncio.async is deprecated since version 3.4.4 use asyncio.ensure_future instead.

Python AsyncioPython 3.4 版本之后引入的一个标准库,它提供了一种异步编程的解决方案,使得 Python 开发者可以更加方便地实现高效的异步 IO 操作。相比于传统的多线程或多进程的方式,异步编程可以更好地利用 CPU 和 IO 资源,提高程序的运行效率。 以下是 Python Asyncio 的一些基本概念和用法: 1. 异步:异步指的是程序在执行 IO 操作时不会阻塞进程,而是在等待 IO 操作完成的同时可以执行其他任务。 2. 协程:协程是一种轻量级的线程,可以在一个线程中并发执行多个协程任务,从而实现异步编程。 3. 事件循环:事件循环是 Asyncio 中的核心概念,它负责调度协程任务的执行,以及处理 IO 事件。 4. Future:Future 是 Asyncio 中用于异步编程的一种对象,它表示一个异步操作的结果,可以用于等待异步操作的完成。 5. Task:Task 是 Future 的子类,表示一个协程任务,它封装了一个协程对象,并可以被事件循环调度执行。 以下是一个简单的使用 Asyncio 实现异步 IO 操作的例子: ```python import asyncio async def fetch_data(): print('Start fetching data...') await asyncio.sleep(1) # 模拟 IO 操作 print('Data fetched!') async def main(): task1 = asyncio.create_task(fetch_data()) task2 = asyncio.create_task(fetch_data()) await task1 await task2 asyncio.run(main()) ``` 在这个例子中,我们定义了一个 fetch_data 协程函数,它模拟了一个 IO 操作,并在操作完成后打印了一条消息。在 main 函数中,我们使用 asyncio.create_task 创建了两个任务,并等待它们的完成。由于这两个任务是并发执行的,所以它们的完成顺序是不确定的。 这只是 Asyncio一个简单示例,如果你想深入了解 Asyncio 的使用和原理,可以参考 Python 官方文档或者一些优秀的 Asyncio 教程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值