neovim / pynvim

Python client and plugin host for Nvim
http://pynvim.readthedocs.io/en/latest/
Apache License 2.0
1.54k stars 120 forks source link

Synchronous calls from an asynchronous context #339

Open numirias opened 6 years ago

numirias commented 6 years ago

I have a use case which I'd think is really common. From an asynchronous context I want to get the result of a synchronous call. E.g., in this asynchronous method I want to access the current buffer content:

class MyPlugin:

    @neovim.autocmd(..., sync=False)
    def my_async_cmd(self):
       # This obviously doesn't work because the function is async
       code = self._vim.current.buffer[:]

Of course, I could hack together my own synchronization construct using e.g. threading.Event:

import threading

class MyPlugin:
    ...
    def _wait_for(self, func):
        event = threading.Event()
        res = None
        def wrapper():
            nonlocal res
            res = func()
            event.set()
        self._vim.async_call(wrapper)
        event.wait()
        return res

With that I could now just use code = self._wait_for(lambda: self._vim.current.buffer[:]) to get the code, but this seems really sketchy and I feel there must be an elegant pattern (which would maybe be more obvious to me if I had a firmer grasp of greenlets).

Could you point me in the right direction here? (I've also read #229 but it doesn't seem to refer to the same problem.)

bfredl commented 6 years ago

the code

class MyPlugin:

    @neovim.autocmd(..., sync=False)
    def my_async_cmd(self):
       # This obviously doesn't work because the function is async
       code = self._vim.current.buffer[:]

should actually work. Using threading.Event should only be necessary for multi-threaded code. An alternative can also be to use expr='some code' as documented in http://pynvim.readthedocs.io/en/latest/usage/remote-plugins.html, to avoid extra RPC overhead, or to be sure to evaluate the expression in context of the autocmd.

numirias commented 6 years ago

@bfredl Thanks for the quick follow-up. I'm sorry, you're absolutely right. The code does indeed work.

In fact, I'm making that call from a thread. In that case, is my solution using threading.Event as good as it gets or is there a more elegant way?

import threading
class MyPlugin:

   @neovim.autocmd(..., sync=False)
   def my_async_cmd(self):

        def func():
            code = self._vim.current.buffer[:]
            ...
        thread = threading.Thread(target=func)
        thread.start()