<div dir="auto"><div dir="auto">How about:<br><div dir="auto"><br></div><div dir="auto">async def wait_to_run(async_fn, *args):</div><div dir="auto">    await wait_for_something()</div><div dir="auto">    return await async_fn(*args)</div><div dir="auto"><br></div><div dir="auto">task = loop.create_task(wait_to_run(myfunc, ...))</div><div dir="auto"><br></div><div dir="auto">-----</div><div dir="auto"><br></div><div dir="auto">Whatever strategy you use, you should also think about what semantics you want if one of these delayed tasks is cancelled before it starts.</div><div dir="auto"><br></div><div dir="auto">For regular, non-delayed tasks, Trio makes sure that even if it gets cancelled before it starts, then it still gets scheduled and runs until the first cancellation point. This is necessary for correct resource hand-off between tasks:</div><div dir="auto"><br></div><div dir="auto">async def some_task(handle):</div><div dir="auto">    with handle:</div><div dir="auto">        await ...</div><div dir="auto"><br></div><div dir="auto">If we skipped running this task entirely, then the handle wouldn't be closed properly; scheduling it once allows the with block to run, and then get cleaned up by the cancellation exception. I'm not sure but I think asyncio handles pre-cancellation in a similar way. (Yury, do you know?)</div><div dir="auto"><br></div><div dir="auto">Now, in delayed task case, there's a similar issue. If you want to keep the same solution, then you might want to instead write:</div><div dir="auto"><br></div><div dir="auto"># asyncio</div><div dir="auto"><div dir="auto" style="font-family:sans-serif">async def wait_to_run(async_fn, *args):</div><div dir="auto" style="font-family:sans-serif">    try:</div><div dir="auto" style="font-family:sans-serif">        await wait_for_something()</div><div dir="auto" style="font-family:sans-serif">    except asyncio.CancelledError:</div><div dir="auto" style="font-family:sans-serif">        # have to create a subtask to make it cancellable</div><div dir="auto" style="font-family:sans-serif">        subtask = loop.create_task(async_fn(*args))</div><div dir="auto" style="font-family:sans-serif">        # then cancel it immediately</div><div dir="auto" style="font-family:sans-serif">        subtask.cancel()</div><div dir="auto" style="font-family:sans-serif">        # and wait for the cancellation to be processed</div><div dir="auto" style="font-family:sans-serif">        return await subtask</div><div dir="auto" style="font-family:sans-serif">    else:</div><div dir="auto" style="font-family:sans-serif">        return await async_fn(*args)</div><div dir="auto" style="font-family:sans-serif"><br></div><div dir="auto" style="font-family:sans-serif">In trio, this could be simplified to</div><div dir="auto" style="font-family:sans-serif"><br></div><div dir="auto" style="font-family:sans-serif"># trio</div><div dir="auto" style="font-family:sans-serif">async def wait_to_run(async_fn, *args):</div><div dir="auto" style="font-family:sans-serif">    try:</div><div dir="auto" style="font-family:sans-serif">        await wait_for_something()</div><div dir="auto" style="font-family:sans-serif">    except trio.Cancelled:</div><div dir="auto" style="font-family:sans-serif">        pass</div><div dir="auto" style="font-family:sans-serif">    return await async_fn(*args)</div><div dir="auto" style="font-family:sans-serif"><br></div><div dir="auto" style="font-family:sans-serif">(This works because of trio's "stateful cancellation" – if the whole thing is cancelled, then as soon as async_fn hits a cancellation point the exception will be re-delivered.)</div><div dir="auto" style="font-family:sans-serif"><br></div><div dir="auto" style="font-family:sans-serif">-n</div></div></div><br><div class="gmail_quote"><div dir="ltr">On Wed, Jun 13, 2018, 13:47 Michel Desmoulin <<a href="mailto:desmoulinmichel@gmail.com" rel="noreferrer noreferrer" target="_blank">desmoulinmichel@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">I was working on a concurrency limiting code for asyncio, so the user<br>
may submit as many tasks as one wants, but only a max number of tasks<br>
will be submitted to the event loop at the same time.<br>
<br>
However, I wanted that passing an awaitable would always return a task,<br>
no matter if the task was currently scheduled or not. The goal is that<br>
you could add done callbacks to it, decide to force schedule it, etc<br>
<br>
I dug in the asyncio.Task code, and encountered:<br>
<br>
    def __init__(self, coro, *, loop=None):<br>
        ...<br>
        self._loop.call_soon(self._step)<br>
        self.__class__._all_tasks.add(self)<br>
<br>
I was surprised to see that instantiating a Task class has any side<br>
effect at all, let alone 2, and one of them being to be immediately<br>
scheduled for execution.<br>
<br>
I couldn't find a clean way to do what I wanted: either you<br>
loop.create_task() and you get a task but it runs, or you don't run<br>
anything, but you don't get a nice task object to hold on to.<br>
<br>
I tried several alternatives, like returning a future, and binding the<br>
future awaiting to the submission of a task, but that was complicated<br>
code that duplicated a lot of things.<br>
<br>
I tried creating a custom task, but it was even harder, setting a custom<br>
event policy, to provide a custom event loop with my own create_task()<br>
accepting parameters. That's a lot to do just to provide a parameter to<br>
Task, especially if you already use a custom event loop (e.g: uvloop). I<br>
was expecting to have to create a task factory only, but task factories<br>
can't get any additional parameters from create_task()).<br>
<br>
Additionally I can't use ensure_future(), as it doesn't allow to pass<br>
any parameter to the underlying Task, so if I want to accept any<br>
awaitable in my signature, I need to provide my own custom ensure_future().<br>
<br>
All those implementations access a lot of _private_api, and do other<br>
shady things that linters hate; plus they are fragile at best. What's<br>
more, Task being rewritten in C prevents things like setting self._coro,<br>
so we can only inherit from the pure Python slow version.<br>
<br>
In the end, I can't even await the lazy task, because it blocks the<br>
entire program.<br>
<br>
Hence I have 2 distinct, but independent albeit related, proposals:<br>
<br>
- Allow Task to be created but not scheduled for execution, and add a<br>
parameter to ensure_future() and create_task() to control this. Awaiting<br>
such a task would just do like asyncio.sleep(O) until it is scheduled<br>
for execution.<br>
<br>
- Add an parameter to ensure_future() and create_task() named "kwargs"<br>
that accept a mapping and will be passed as **kwargs to the underlying<br>
created Task.<br>
<br>
I insist on the fact that the 2 proposals are independent, so please<br>
don't reject both if you don't like one or the other. Passing a<br>
parameter to the underlying custom Task is still of value even without<br>
the unscheduled instantiation, and vice versa.<br>
<br>
Also, if somebody has any idea on how to make a LazyTask that we can<br>
await on without blocking everything, I'll take it.<br>
<br>
<br>
<br>
_______________________________________________<br>
Python-Dev mailing list<br>
<a href="mailto:Python-Dev@python.org" rel="noreferrer noreferrer noreferrer" target="_blank">Python-Dev@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-dev" rel="noreferrer noreferrer noreferrer noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/python-dev</a><br>
Unsubscribe: <a href="https://mail.python.org/mailman/options/python-dev/njs%40pobox.com" rel="noreferrer noreferrer noreferrer noreferrer" target="_blank">https://mail.python.org/mailman/options/python-dev/njs%40pobox.com</a><br>
</blockquote></div></div>