分享及交流: 将现有函数魔改成async 函数

我经常使用 try 这个package 来尝试新的package, 但是无论是安装还是尝试新的package , 都需要调用 package-refresh-contents, 这个又是个耗时的同步函数,每次阻塞着都觉得很麻烦,所以我用 emacs-asyncpackage-refresh-contents 进行了魔改, 并且抽象了一个宏

;;; http://lists.gnu.org/archive/html/help-gnu-emacs/2008-06/msg00087.html
(defmacro samray/measure-time (&rest body)
  "Measure the time it takes to evaluate BODY."
  `(let ((time (current-time)))
     ,@body
     (message "%.06f" (float-time (time-since time)))))

(defmacro samray/async-function (current-func)
  "Wrap function CURRENT-FUNC with `async`."
  (make-thread (lambda ()
		 `(let ((elapsed-time (samray/measure-time (,current-func))))
		    (message "Run `%s` with thread, elapsed time is %s" ',current-func ,elapsed-time))))
  )
(samray/async-function package-refresh-contents)

只不过现在这个宏只适用 package-refresh-contents 这种无参数的函数, 我想让其通用一点, 无论有没有参数都适用, 大家有啥建议么

可以看我写的 yaoddmuse.el

https://www.emacswiki.org/emacs/download/yaoddmuse.el 主要指的是哪部分?

post部分,或者干脆看auto-install.el , 我十几年前就用auto-install.el异步安装了

你误解我的意思了,我想要实现的不是异步安装,而是通过 macro/advice 将一个现有的函数魔改成异步函数, 只是异步安装是我的一个例子而已

把原来那个函数copy出来,然后加上异步回调就可以了

似乎你还是没有理解我的意思,我想做的是用async 对原来的函数进行包装,可以随便包装新的函数,而不需要copy, 每次都copy 实在是太可怕

我知道你的意思,但是有些函数因为设计时没有考虑异步,函数之间耦合很紧,你开始只想异步一个函数,但是最后需要wrap一堆函数。

还不如copy关键函数做异步变动

这也是当年我重写yaoddmuse.el 而不是魔改oddmuse.el 魔改还不如重写一个,魔改太脏了

有些函数设计的时候没有考虑异步,如果我用 Emac26的thread呢,

(defmacro samray/async-function (current-func)
  "Wrap function CURRENT-FUNC with `async`."
  (make-thread (lambda ()
		 `(let ((elapsed-time (samray/measure-time (,current-func))))
		    (message "Run `%s` with thread, elapsed time is %s" ',current-func ,elapsed-time))))
  )

不过这个macro 似乎有问题, 我又不知道问题出在哪里, 宏虽然强大,但很容易跑偏.

听起来很怪,应该有不需要调用的使用方式,但你没有发现。

这基本等于

M-! emacs -Q -f package-refresh-contents &

大概率不是你想象的那样,它很有可能不会正常工作,考虑到:

  1. 它不会管你的 package.el 配置,比如你用了 Melpa 的话
  2. 它不会影响当前的 Emacs 的状态

async.el 没有魔法。

我本意是通过 async/thread 来避免那些耗时函数阻塞, 比如安装package 时, 或者是使用 tramp, 似乎如你所说,并没有魔法

async.el 适合用来做些没有副作用的事情。

但是偶尔如果需要副作用,也是可以的, 以 async-start 为例:

(async-start START-FUNC &optional FINISH-FUNC)

START-FUNC 就是那个异步过程,如果里边需要当前 Emacs 的变量/函数/包,需要想办法一一导入。FINISH-FUNC 是回调函数,在这里让副作用生效。有多少工作量,就看依赖是不是很复杂了。

用它来实现 package 异步安装,恐怕不能如愿。因为安装需要 package-refresh-contents 的副作用,即需要用到 refresh 返回的数据,所以要等异步调用完成才能够开始安装。而不是启个线程/进程更新,然后马上安装:

(async-start
 ;; START-FUNC
 (lambda ()
   (package-refresh-contents))
 ;; FINISH-FUNC
 (lambda ()
   (package-read-all-archive-contents)))

(package-install 'foo-package)

这里的 install 在异步操作完成之前就执行了,没有达到预想的效果,除非放在 FINISH-FUNC 里。所以当把一个函数变成异步之后,原先的流程就要变了,而且你这个 async-function 还不知道异步过程什么时候结束,实用性就更低了。

另外,其实 package-refresh-contents 本身是有个异步参数的

(package-refresh-contents &optional ASYNC)

是的,参加 package-alist 变量,直接手动安装就不用刷新了

(package-install (package-desc-create :name n :version v :dir 'builtin :kind k :archive "melpa"))

这么做就需要知道具体版本号了

楼 歪 了

为什么没有人提提 mutex

版本号这个变量也是有的 就是最新的一个

对于一些primary函数,比如dired-do-copy引用的,emacs 26引入的thread没法解决阻塞的问题;tramp有一个thread-safe branch,看样子不是简单魔改就能解决的