Thank you all for your help!
I made some experiments with the following code:
import sublime
import sublime_plugin
import functools
import time
class AsyncOperationCommand(sublime_plugin.TextCommand):
"""Calls an asynchronous operation, with a callback to be executed
synchronously afterwards.
"""
def run(self, edit):
print("The command '{}' has been triggered.".format(
self.__class__.__name__))
sublime.set_timeout_async(
functools.partial(
self.async_method, self.view,
functools.partial(self.view.run_command, "sleep")),
2000)
print("timeout is set")
def async_method(self, view, callback):
""" A method to be run asynchronously, and execute callback when finish
"""
print("inside async_method")
if view.is_valid():
print("View is valid!")
else:
print("View is invalid!")
time.sleep(5)
print("done sleeping")
if view.is_valid():
print("View is valid!")
else:
print("View is invalid!")
callback()
class SleepCommand(sublime_plugin.TextCommand):
"""Performs a sleep of 2 seconds. Power nap!"""
def run(self, edit):
print("The command '{}' has been triggered.".format(
self.__class__.__name__))
time.sleep(2)
print("SLEEPING TIME IS OVER")
I played with it by executing sublime.active_window().active_view().run_command("async_operation")
in the console.
I found out that:
- The
view
object can become invalid in the middle of async_method
(by closing the active view)
- Invoking
view.run_command()
on an invalid view does nothing (no exception, no return value)
- It is possible to invoke a synchronous operation (on the main thread) from an asynchronous operation (on a separate thread)
Conclusion
A text command always starts in the main thread, running synchronously. To make it run asynchronously, put the logic in a separate function, and invoke it from the text command - either via sublime.set_timeout_async()
or using a thread instance.
However, manipulating a view
(for example, with view.insert()
) inside the asynchronous function is dangerous, since the view
might become invalid (closed by the user) at any point in time! In order to solve this, the logic which involves view
manipulation must execute on the main thread, synchronously.
This can be acheived in two ways:
The first way is to create a supplementary text command which contains the view
manipulation logic. The asynchronous function will invoke that supplementary command via view.run_command()
, and as I stated at the begginning, a text command always executes on the main thread.
A code example for the first way can be found right above this section.
The second way is to create a variable and a function. The variable stores a boolean telling whether the asynchronous operation has finished, and the function checks that variable. If the variable’s value is true
, the operation continues. If the value is false
, sublime.set_timeout(function)
is called, and the function returns. The text command should contain a call for that function at its bottom.
A code example for the second way can be found in this awesome guide.
Each way has its pros and cons, and I wish there was a better/easier way. But at least I got it all figured out now, thanks to you guys
So thank you very much, and I hope that my findings will help others as well.