asyncio:仅在所有其他任务正在等待时才运行任务

时间:2019-05-26 00:30:37

标签: python concurrency python-asyncio

我目前正在使用asyncio.wait

运行一些无尽的任务

我需要一个特殊的函数来在所有其他await上运行

import asyncio 

async def special_function():
    while True:
        # does some work, 
        # Passes control back to controller to run main_tasks
        # if they are no longer waiting.
        await asyncio.sleep(0)

async def handler():
    tasks = [task() for task in main_tasks]

    # Adding the task that I want to run when all main_tasks are awaiting:
    tasks.append(special_function())

    await asyncio.wait(tasks)

asyncio.get_event_loop().run_until_complete(handler())

如何让special_function仅在所有main_tasks都在await上时运行?


编辑:

我的意思是“所有main_tasks都在await上:所有main_tasks还没有准备好继续,例如处于asyncio.sleep(100)或I / O绑定中,并且仍在等待数据。

因此,main_tasks无法继续执行,并且任务处于这种状态时,事件循环运行special_function,而不是事件循环的每次迭代。


编辑2:

我的用例:

main_tasks正在使用来自Web套接字的新数据更新数据结构。

special_function根据来自该进程的更新信号将该数据传输到另一个进程。 (multiprocessing具有共享变量和数据结构)

它必须是传输时最新的数据,不能有来自main_tasks的待处理更新。

这就是为什么我只在没有main_tasks包含新数据可处理的情况下才想运行special_function的原因。 (即所有在await上等待)

5 个答案:

答案 0 :(得分:3)

我试图为“任务未准备好运行”条件编写测试。我认为asyncio不会公开调度程序的详细信息。开发人员明确表示,他们希望保留更改异步内部结构的自由,而又不会破坏向后兼容性。

asyncio.Task中有此注释(注意:_step()运行任务协程直到下一次等待):

# An important invariant maintained while a Task not done:
#   
# - Either _fut_waiter is None, and _step() is scheduled;
# - or _fut_waiter is some Future, and _step() is *not* scheduled.

但是,该内部变量当然不在API中。

通过读取_fut_waiter的输出,您可以对repr(task)进行有限的访问,但是格式似乎也不可靠,因此我不会依赖这种方式:

PENDINGMSG = 'wait_for=<Future pending '

if all(PENDINGMSG in repr(t) for t in monitored_tasks):
    do_something()

无论如何,我认为您正在尝试变得过于完美。您想知道其他任务中是否有新数据。如果数据在异步缓冲区中怎么办?内核缓冲区?网卡接收缓冲区? ...您永远不知道下一毫秒是否会收到新数据。

我的建议:将所有更新写入单个队列。检查该队列作为唯一的更新源。如果队列为空,请发布最后一个状态。

答案 1 :(得分:2)

这就是我要做的:

  1. 我不会使用您的特殊功能。

  2. 每次数据更新都需要一个单独的世代ID(4字节整数),我只将ID放在共享内存中。

我认为两个进程都是独立运行的。

  1. 订户将世代ID保留为本地。当它发现共享内存中的世代ID已更改时,则将从文件中读取新数据。

  2. 数据存储在tmpfs(/ tmp)上,因此它在内存中。如果合适,您可以创建自己的tmpfs。很快。

这是原因:

  • 为确保订户不会在共享内存中获取半生数据,必须使用信号量对其进行保护。是皮塔饼
  • 通过使用文件,您可以携带可变大小的数据。这可能不适用于您。使用共享内存时要解决的困难之一是要有足够的空间,而又不要浪费空间。使用文件可以解决此问题。
  • 通过使用4字节的int生成ID,更新ID是原子的。这是一个巨大的优势。

因此,当您的一项任务收到新数据时,打开一个文件,对其进行写入,然后在关闭文件描述符后,将世代ID写入共享内存。在更新世代ID之前,您可以安全地删除文件。订户-如果已打开文件,它将完成文件的读取,如果尝试打开它,则它将无法打开,因此无论如何都必须等待下一代。 如果计算机崩溃,则/ tmp消失了,因此您不必担心清理文件。您甚至可以编写一个新任务,即如果需要的话,可以删除/ tmp中较早的文件。

答案 2 :(得分:1)

当事件循环运行某些任务时,此任务将一直执行,直到将控制权返回给事件循环为止。通常,只有一个原因是任务希望将控制权返回到事件循环:任务如果遇到阻塞操作(因此“准备不继续”)。

这意味着“事件循环的每次迭代”通常等于“所有main_tasks都在await上”。您已经拥有的代码将(通常)根据需要工作。您唯一要做的就是to make special_function()任务。


在遇到“真正的”阻塞调用之前,任务很有可能将控制权返回事件循环,通常看起来像await asyncio.sleep(0)(就像您在special_function中所做的那样)。这意味着任务要确保所有其他任务在继续执行之前被调用:您可能希望尊重这一点。

答案 3 :(得分:0)

为什么不使用semaphore

async def do_stuff(semaphore):
    async with semaphore:
      await getting_stuff_done()

async def wait_til_everyone_is_busy(semaphore):
    while not semaphore.locked():
      await asyncio.sleep(1)
    do_other_stuff()

为了更好地说明我的观点,请举一个简单的例子:

import asyncio
import time

async def process(semaphore, i):
    while True:
        print(f"{i} I'm gonna await")
        await asyncio.sleep(1)
        async with semaphore:
            print(f'{i} sleeping')
            await asyncio.sleep(3)
        print(f'{i} done sleeping')
        print(f"{i} I'm gonna await again")
        await asyncio.sleep(1)

async def other_process(semaphore):
    while True:
        while not semaphore.locked():
            print("Everyone is awaiting... but I'm not startingr")
            await asyncio.sleep(1)
        print("Everyone is busy, let's do this!")
        time.sleep(5)
        print('5 seconds are up, let everyone else play again')
        await asyncio.sleep(1)

semaphore = asyncio.Semaphore(10)
dataset = [i for i in range(10)]
loop = asyncio.new_event_loop()
tasks = [loop.create_task(process(semaphore, i)) for i in dataset]
tasks.append(loop.create_task(other_process(semaphore)))
loop.run_until_complete(asyncio.wait(tasks))

我们创建10个使用“进程”功能的任务,以及一个使用“ other_process”的任务。一个执行“ other_process”的程序只能在所有其他程序都保持该信号量的同时运行,并且由于Asyncio上下文切换的工作方式,只有在其他程序处于等待状态时才执行“ other_process”功能,直到“ other_process”命中自己的“ await”。

$ python3 tmp
0 I'm gonna await
1 I'm gonna await
2 I'm gonna await
3 I'm gonna await
4 I'm gonna await
5 I'm gonna await
6 I'm gonna await
7 I'm gonna await
8 I'm gonna await
9 I'm gonna await
Everyone is awaiting... but I'm not startingr
0 sleeping
1 sleeping
2 sleeping
3 sleeping
4 sleeping
5 sleeping
6 sleeping
7 sleeping
8 sleeping
9 sleeping
Everyone is busy, let's do this!
5 seconds are up, let everyone else play again
0 done sleeping
0 I'm gonna await again
1 done sleeping
1 I'm gonna await again
2 done sleeping
2 I'm gonna await again
3 done sleeping
3 I'm gonna await again
4 done sleeping
4 I'm gonna await again
5 done sleeping
5 I'm gonna await again
6 done sleeping
6 I'm gonna await again
7 done sleeping
7 I'm gonna await again
8 done sleeping
8 I'm gonna await again
9 done sleeping
9 I'm gonna await again
Everyone is awaiting... but I'm not startingr
0 I'm gonna await
1 I'm gonna await
2 I'm gonna await
3 I'm gonna await
4 I'm gonna await
5 I'm gonna await
6 I'm gonna await
7 I'm gonna await
8 I'm gonna await
9 I'm gonna await
Everyone is awaiting... but I'm not startingr
0 sleeping
1 sleeping
2 sleeping
3 sleeping
4 sleeping
5 sleeping
6 sleeping
7 sleeping
8 sleeping
9 sleeping
Everyone is busy, let's do this!

答案 4 :(得分:0)

将输入和输出请求都推送到PriorityQueue上,输入优先于输出。然后只需正常处理队列中的任务,它将始终在所有输出请求之前完成所有未完成的输入请求。

所以您的主循环将包含以下内容:

  • InputListener1(排队以优先级0接收的每个InputTask1)
  • InputListener2(排队以优先级0接收的每个InputTask2)
  • InputListener3(排队以优先级0接收的每个InputTask3)
  • OutputListener(排队以优先级1接收的每个OutputTask)
  • QueueWorker(处理队列中的下一个任务)

这可能意味着您必须将所有现有任务的逻辑拆分为单独的套接字侦听器和实际任务处理,但这不一定是一件坏事。