<div dir="ltr"><div>OK, I think I understand your primary use case -- the C++ library calls callbacks in their own threads but you want the callback code to run in your event loop, where presumably it is structured as a coroutine and may use `yield from` or `await` to wait for other coroutines, tasks or futures. Then when that coroutine is done it returns a value which your machinery passes back as the result of a concurrent.futures.Future on which the callback thread is waiting.<br><br></div>I don't think the use case involving multiple event loops in different threads is as clear. I am still waiting for someone who is actually trying to use this. It might be useful on a system where there is a system event loop that must be used for UI events (assuming this event loop can somehow be wrapped in a custom asyncio loop) and where an app might want to have a standard asyncio event loop for network I/O. Come to think of it, the ProactorEventLoop on Windows has both advantages and disadvantages, and some app might need to use both that and SelectorEventLoop. But this is a real pain (because you can't share any mutable state between event loops).<br><div><br><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Sun, Sep 27, 2015 at 6:36 AM, Vincent Michel <span dir="ltr"><<a href="mailto:vxgmichel@gmail.com" target="_blank">vxgmichel@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi Guido,<br>
<br>
Thanks for your interest,<br>
<br>
I work for a synchrotron and we use the distributed control system<br>
TANGO. The main implementation is in C++, but we use a python binding<br>
called PyTango. The current server implementation (on the C++ side)<br>
does not feature an event loop but instead create a different thread<br>
for each client.<br>
<br>
TANGO: <a href="http://www.tango-controls.org/" rel="noreferrer" target="_blank">http://www.tango-controls.org/</a><br>
PyTango: <a href="http://www.esrf.eu/computing/cs/tango/tango_doc/kernel_doc/pytango/latest/index.html" rel="noreferrer" target="_blank">http://www.esrf.eu/computing/cs/tango/tango_doc/kernel_doc/pytango/latest/index.html</a><br>
<br>
I wanted to add asyncio support to the library, so that we can benefit<br>
from single-threaded asynchronous programming. The problem is that<br>
client callbacks run in different threads and there is not much we can<br>
do about it until a pure python implementation is developed (and it's<br>
a lot of work). Instead, it is possible to use an asyncio event loop,<br>
run the server through run_in_executor (juste like you mentioned in<br>
your mail), and redirect all the client callbacks to the event loop.<br>
That's the part where job submission from a different thread comes in<br>
handy.<br>
<br>
A very similar solution has been developed using gevent, but I like<br>
explicit coroutines better :p<br>
<br>
Another use case is the communication between two event loops. From<br>
what I've seen, the current context (get/set event loop) is only<br>
related to the current thread. It makes it easy to run different event<br>
loops in different threads. Even though I'm not sure what the use case<br>
is, I suppose it's been done intentionally. Then the executor<br>
interface is useful to run things like:<br>
<br>
executor = LoopExecutor(other_loop)<br>
result = await my_loop.run_in_executor(executor, coro_func, *args)<br>
<br>
There is working example in the test directory:<br>
<a href="https://github.com/vxgmichel/asyncio-loopexecutor/blob/master/test/test_multi_loop.py" rel="noreferrer" target="_blank">https://github.com/vxgmichel/asyncio-loopexecutor/blob/master/test/test_multi_loop.py</a><br>
<br>
***<br>
<br>
The coroutine(fn) cast only makes sense if a subclass of Executor is<br>
used, in order to be consistent with the Executor.submit signature.<br>
Otherwise, passing an already-called coroutine is perfectly fine. I<br>
think it is a good idea to define a simple submit function like you<br>
recommended:<br>
<br>
def submit_to_loop(loop, coro):<br>
    future = concurrent.futures.Future()<br>
    callback = partial(schedule, coro, destination=future)<br>
    loop.call_soon_threadsafe(callback)<br>
    return future<br>
<br>
And then use the executor interface if we realize it is actually<br>
useful. It's really not a lot of code anyway:<br>
<br>
class LoopExecutor(concurrent.futures.Executor):<br>
<br>
    def __init__(self, loop=None):<br>
        self.loop = loop or asyncio.get_event_loop()<br>
<br>
    def submit(self, fn, *args, **kwargs):<br>
        coro = asyncio.coroutine(fn)(*args, **kwargs)<br>
        return submit_to_loop(self.loop, coro)<br>
<br>
I'll update the repository.<br>
<br>
Cheers,<br>
<br>
Vincent<br>
<div class="HOEnZb"><div class="h5"><br>
2015-09-27 4:52 GMT+02:00 Guido van Rossum <<a href="mailto:guido@python.org">guido@python.org</a>>:<br>
><br>
> Hi Vincent,<br>
><br>
> I've read your write-up with interest. You're right that it's a bit awkward to make calls from the threaded world into the asyncio world. Interestingly, there's much better support for passing work off from the asyncio event loop to a thread (run_in_executor()). Perhaps that's because the use case there was obvious from the start: some things that may block for I/O just don't have an async interface yet, so in order to use them from an asyncio task they must be off-loaded to a separate thread or else the entire event loop is blocked. (This is used for calling getaddrinfo(), for example.)<br>
><br>
> I'm curious where you have encountered the opposite use case?<br>
><br>
> I think if I had to do this myself I would go for a more minimalist interface: something like your submit() method but without the call to asyncio.coroutine(fn). Having the caller pass in the already-called coroutine object might simplify the signature even further. I'm not sure I see the advantage of trying to make this an executor -- but perhaps I'm missing something?<br>
><br>
> --Guido<br>
><br>
><br>
><br>
> On Sat, Sep 26, 2015 at 7:29 AM, Vincent Michel <<a href="mailto:vxgmichel@gmail.com">vxgmichel@gmail.com</a>> wrote:<br>
>><br>
>> Hi,<br>
>><br>
>> I noticed there is currently no standard solution to submit a job from a thread to an asyncio event loop.<br>
>><br>
>> Here's what the asyncio documentation says about concurrency and multithreading:<br>
>><br>
>> > To schedule a callback from a different thread, the BaseEventLoop.call_soon_threadsafe() method should be used.<br>
>> > Example to schedule a coroutine from a different thread:<br>
>> >     loop.call_soon_threadsafe(asyncio.async, coro_func())<br>
>><br>
>> The issue with this method is the loss of the coroutine result.<br>
>><br>
>> One way to deal with this issue is to connect the asyncio.Future returned by async (or ensure_future) to a concurrent.futures.Future. It is then possible to use a subclass of concurrent.futures.Executor to submit a callback to an asyncio event loop. Such an executor can also be used to set up communication between two event loops using run_in_executor.<br>
>><br>
>> I posted an implementation called LoopExecutor on GitHub:<br>
>> <a href="https://github.com/vxgmichel/asyncio-loopexecutor" rel="noreferrer" target="_blank">https://github.com/vxgmichel/asyncio-loopexecutor</a><br>
>> The repo contains the loopexecutor module along with tests for several use cases. The README describes the whole thing (context, examples, issues, implementation).<br>
>><br>
>> It is interesting to note that this executor is a bit different than ThreadPoolExecutor and ProcessPoolExecutor since it can also submit a coroutine function. Example:<br>
>><br>
>> with LoopExecutor(loop) as executor:<br>
>>     future = executor.submit(operator.add, 1, 2)<br>
>>     assert future.result() == 3<br>
>>     future = executor.submit(asyncio.sleep, 0.1, result=3)<br>
>>     assert future.result() == 3<br>
>><br>
>> This works in both cases because submit always cast the given function to a coroutine. That means it would also work with a function that returns a Future.<br>
>><br>
>> Here's a few topic related to the current implementation that might be interesting to discuss:<br>
>><br>
>> - possible drawback of casting the callback to a coroutine<br>
>> - possible drawback of concurrent.future.Future using asyncio.Future._copy_state<br>
>> - does LoopExecutor need to implement the shutdown method?<br>
>> - removing the limitation in run_in_executor (can't submit a coroutine function)<br>
>> - adding a generic Future connection function in asyncio<br>
>> - reimplementing wrap_future with the generic connection<br>
>> - adding LoopExecutor to asyncio (or concurrent.futures)<br>
>><br>
>> At the moment, the interaction between asyncio and concurrent.futures only goes one way. It would be nice to have a standard solution (LoopExecutor or something else) to make it bidirectional.<br>
>><br>
>> Thanks,<br>
>><br>
>> Vincent<br>
>><br>
>><br>
>> _______________________________________________<br>
>> Python-ideas mailing list<br>
>> <a href="mailto:Python-ideas@python.org">Python-ideas@python.org</a><br>
>> <a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/python-ideas</a><br>
>> Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/codeofconduct/</a><br>
><br>
><br>
><br>
><br>
> --<br>
> --Guido van Rossum (<a href="http://python.org/~guido" rel="noreferrer" target="_blank">python.org/~guido</a>)<br>
</div></div></blockquote></div><br><br clear="all"><br>-- <br><div class="gmail_signature">--Guido van Rossum (<a href="http://python.org/~guido" target="_blank">python.org/~guido</a>)</div>
</div>