Using Emacs Threads To Execute Commands Asynchronously
1 Executive Summary
Emacs 26 has threads for executing functions asynchronously.
Emacs
commands that call an external process and wait for that process
to
finish make a good candidate for asynchronous execution —
e.g.,
smtpmail-send-it for sending mail. The arrival of
threads provides
an interesting option for running such commands
asynchronously.
2 First
Attempt — Custom Async gnus Command
I initially wrote a custom command for launching
gnus asynchronously
— it was a one-line function that ran the following:
(make-thread #'gnus)
The above worked well — except when command
gnus needed user input
— so I just had to be thoughtful about when I called it. But
a few
weeks later, I wanted the equivalent for function
smtpmail-send-it
for sending mail. I almost wrote myself one more command before
stepping back to create a more generic solution.
3 One Command To Thread Them All
I have now defined command
emacspeak-wizards-execute-asynchronously
bound to C-' a.
Note that this command, though part of module
emacspeak-wizards, has
no emacspeak dependencies.
(defun emacspeak-wizards-execute-asynchronously (key) "Read key-sequence, then execute its command on a new thread." (interactive (list (read-key-sequence "Key Sequence: "))) (let ((l (local-key-binding key)) (g (global-key-binding key))) (cond ( (commandp l) (make-thread l) (message "Running %s on a new thread." l)) ((commandp g) (make-thread g) (message "Running %s on a new thread." g)) (t (error "%s is not bound to a command." key))))) (global-set-key (kbd "C-' a") 'emacspeak-wizards-execute-asynchronously)
With this command bound to C-' a, I can now get rid
of my custom
gnus-async command and its associated key-binding. I
already have
command gnus bound to C-; g, so I can
just press C-' a C-; g to
fetch news/mail asynchronously.
Similarly, when sending mail using smtpmail I can
press C-' a C-c in the
C-c*mail* buffer to send mail without
Emacs blocking.
4 Final Caveats
Like other asynchronous solutions (see package
async for instance)
one needs to make sure that the command being executed
asynchronously
will not require user input. In the case of package
async, the
asynchronous Emacs will block waiting for input; in the case of
make-thread, Emacs enters a blocking loop with the
minibuffer
continuously displaying
No catch for ...
The only way to come out is to kill Emacs — so make sure
to use
command emacspeak-wizards-execute-asynchronously only
when you're
sure that the command being run asynchronously will not require
user
input.