<div dir="ltr">Do you want to propose a minimal patch to asyncio? A PR for <a href="https://github.com/python/asyncio">https://github.com/python/asyncio</a> would be the best thing to do. I'd leave the LoopExecutor out of it for now. The code could probably live at the bottom of futures.py.<br></div><div class="gmail_extra"><br><div class="gmail_quote">On Sun, Sep 27, 2015 at 1:29 PM, 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">Yes that's exactly it. No problem for the multiple event loops, it was<br>
a fun thing to play with. Then there's probably no reason to have a<br>
loop executor either.<br>
<br>
I think the important part is really the interface between asyncio<br>
futures and concurrent futures, since it is not trivial to write and<br>
maintain. In particular, getting exceptions and cancellation to work<br>
safely can be a bit tricky.<br>
<div class="HOEnZb"><div class="h5"><br>
<br>
2015-09-27 18:42 GMT+02:00 Guido van Rossum <<a href="mailto:guido@python.org">guido@python.org</a>>:<br>
> OK, I think I understand your primary use case -- the C++ library calls<br>
> callbacks in their own threads but you want the callback code to run in your<br>
> event loop, where presumably it is structured as a coroutine and may use<br>
> `yield from` or `await` to wait for other coroutines, tasks or futures. Then<br>
> when that coroutine is done it returns a value which your machinery passes<br>
> back as the result of a concurrent.futures.Future on which the callback<br>
> thread is waiting.<br>
><br>
> I don't think the use case involving multiple event loops in different<br>
> threads is as clear. I am still waiting for someone who is actually trying<br>
> to use this. It might be useful on a system where there is a system event<br>
> loop that must be used for UI events (assuming this event loop can somehow<br>
> be wrapped in a custom asyncio loop) and where an app might want to have a<br>
> standard asyncio event loop for network I/O. Come to think of it, the<br>
> ProactorEventLoop on Windows has both advantages and disadvantages, and some<br>
> app might need to use both that and SelectorEventLoop. But this is a real<br>
> pain (because you can't share any mutable state between event loops).<br>
><br>
><br>
><br>
> On Sun, Sep 27, 2015 at 6:36 AM, Vincent Michel <<a href="mailto:vxgmichel@gmail.com">vxgmichel@gmail.com</a>> wrote:<br>
>><br>
>> 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:<br>
>> <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>
>><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>
>><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<br>
>> > awkward to make calls from the threaded world into the asyncio world.<br>
>> > Interestingly, there's much better support for passing work off from the<br>
>> > asyncio event loop to a thread (run_in_executor()). Perhaps that's because<br>
>> > the use case there was obvious from the start: some things that may block<br>
>> > for I/O just don't have an async interface yet, so in order to use them from<br>
>> > an asyncio task they must be off-loaded to a separate thread or else the<br>
>> > entire event loop is blocked. (This is used for calling getaddrinfo(), for<br>
>> > 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<br>
>> > interface: something like your submit() method but without the call to<br>
>> > asyncio.coroutine(fn). Having the caller pass in the already-called<br>
>> > coroutine object might simplify the signature even further. I'm not sure I<br>
>> > see the advantage of trying to make this an executor -- but perhaps I'm<br>
>> > 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>><br>
>> > wrote:<br>
>> >><br>
>> >> Hi,<br>
>> >><br>
>> >> I noticed there is currently no standard solution to submit a job from<br>
>> >> a thread to an asyncio event loop.<br>
>> >><br>
>> >> Here's what the asyncio documentation says about concurrency and<br>
>> >> multithreading:<br>
>> >><br>
>> >> > To schedule a callback from a different thread, the<br>
>> >> > 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<br>
>> >> returned by async (or ensure_future) to a concurrent.futures.Future. It is<br>
>> >> then possible to use a subclass of concurrent.futures.Executor to submit a<br>
>> >> callback to an asyncio event loop. Such an executor can also be used to set<br>
>> >> 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<br>
>> >> use cases. The README describes the whole thing (context, examples, issues,<br>
>> >> implementation).<br>
>> >><br>
>> >> It is interesting to note that this executor is a bit different than<br>
>> >> ThreadPoolExecutor and ProcessPoolExecutor since it can also submit a<br>
>> >> 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<br>
>> >> to a coroutine. That means it would also work with a function that returns a<br>
>> >> Future.<br>
>> >><br>
>> >> Here's a few topic related to the current implementation that might be<br>
>> >> interesting to discuss:<br>
>> >><br>
>> >> - possible drawback of casting the callback to a coroutine<br>
>> >> - possible drawback of concurrent.future.Future using<br>
>> >> 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<br>
>> >> 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<br>
>> >> only goes one way. It would be nice to have a standard solution<br>
>> >> (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>
><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>